从我读到的其他SO答案,像这个和这个,编译器把源代码转换成目标文件。 对象文件可能包含对需要由链接器解析的printf
等函数的引用。
我不明白的是,当声明和定义都存在于同一个文件中时,像下面的情况,编译器或链接器是否解析对return1
的引用?
或者这只是编译器优化的一部分?
int return1();
int return2() {
int b = return1();
return b + 1;
}
int return1() {
return 1;
}
int main() {
int b = return2();
}
我通过运行g++-e main.cpp
确保预处理与此无关。
看情况。 这两个选项都是可能的,您没有提到的选项也是可能的,比如编译器或链接器重新排列代码,以便不再存在任何函数。 编译器发出对函数的引用,链接器解析这些引用,作为理解C++的一种方式,这是很好的想法,但是要记住,编译器和链接器所要做的就是生成一个工作的程序,有许多不同的方法可以做到这一点。
然而,编译器和链接器必须做的一件事是确保对标准库函数的任何调用(就像您提到的printf
)都是按照C++源代码指定的顺序进行的。 除此之外(以及其他一些类似的担忧),他们或多或少都可以随心所欲。
[lex.phases]/1.9涉及翻译的最后阶段,其中[着重号为mine]:
将解析所有外部实体引用。 库组件被链接以满足对当前翻译中未定义的实体的外部引用。 所有这样的翻译器输出被收集到一个程序映像中,该映像包含在其执行环境中执行所需的信息。
然而,由编译器决定库组件是单个翻译单元还是它们的组合; 由[分立]/2[重点为我]:
[注:以前翻译过的翻译单元和实例化单元可以单独保存或保存在库中。程序的单独翻译单元通过(例如)调用标识符具有外部链接的函数,操作标识符具有外部链接的对象或操作数据文件来进行通信([Basic.Link])。翻译单元可以单独翻译,然后再链接,以产生可执行程序。-结束注]
OP:[...] 编译器或链接器是否解析对return1
的引用?
因此,即使return1
具有外部链接,因为它是在引用它的翻译单元中定义的(return2
),链接器也不应该需要解析对它的引用,因为它的定义存在于当前翻译中。 然而,标准段落(很可能是故意的)对于当需要链接以满足外部引用时的要求有点模糊,我认为将解析return2
中的return1
引用推迟到链接阶段是不符合要求的实现。
实际上,问题出在下面的代码上:
static int return1();
int return2() {
int b = return1();
return b + 1;
}
int return1() {
return 1;
}
链接器的问题是,每个翻译单元现在都可以包含自己的return1
,因此链接器在选择正确的return1
时会遇到问题。 围绕这一点有一些窍门,例如将翻译单元名称添加到函数名称中。 大多数ABI不这样做,但C++标准允许这样做。 但是对于匿名名称空间,例如namespace{int function1();}
,ABI将使用这样的技巧。