当我在头文件中声明一个函数并将该函数的定义放在其他文件中时,编译器/链接器如何找到定义?它会系统地搜索其路径中的每个文件,还是有更优雅的解决方案?在过去的几天里,这一直困扰着我,我一直无法找到解释。
3 回答
编译器不会这样做,链接器会这样做。
虽然编译器一次只处理一个源文件,但当调用链接器时,它会传递编译器生成的所有目标文件的名称,以及用户希望链接的任何库的名称。因此,链接器完全了解可能包含定义的文件集,它只需要查看这些目标文件的符号表。除此之外,它不需要进行任何搜索。
例如,假设您有 foo.h 和 foo.c 定义和实现 function foo()
,而 bar.h 和 bar.c 定义和实现bar()
。说bar
调用foo
,以便 bar.c 包含 foo.h。此编译分为三个步骤:
gcc -c foo.c
gcc -c bar.c
gcc foo.o bar.o -o program
第一行编译 foo.c,生成 foo.o。第二个编译 bar.c,生成 bar.o。此时,在目标文件bar.o中,foo
是一个外部符号。第三行调用链接器,它将 foo.o 和 bar.o 链接到一个名为“program”的可执行文件中。当链接器处理 bar.o 时,它会看到未解析的外部符号foo
,因此它会查看所有其他正在链接的目标文件(在本例中为 foo.o)的符号表并foo
在 foo.o 中找到,并完成关联。
对于库,这有点复杂,它们出现在命令行上的顺序可能取决于您的链接器,但通常是相同的原则。
当您编译 .cpp 文件时,编译器会在 .obj 文件中输出两个表:它期望在外部定义的符号列表,以及在该特定模块中定义的符号列表。
链接器获取编译器输出的所有 .obj 文件,然后(顾名思义)将它们链接在一起。因此,对于每个模块,它会查看标记为“外部定义”的符号列表,并查看为这些符号提供的所有其他模块。
所以它只会“搜索”你告诉它搜索的模块。
如果它在任何其他模块中都找不到该符号,那就是您收到“未定义引用”错误的时候。
假设您有一个带有 #include foo.h 的 foo.cpp 并且可能还有其他包含。标头当然可以有自己的#include-s。
预处理器将从 foo.cpp 开始,解析 #includes 并读取标头内容。结果将是来自头文件和 foo.cpp “扁平化”的文本。然后编译器将处理该文本。如果一个变量/函数/等应该被声明在一个头文件的某个地方没有找到,编译器将报告一个错误。
基本点是编译器必须将其所有声明视为 .cpp 和标头的结果。