源文件到可执行文件的过程分为三个阶段:编译阶段,链接阶段,生成可执行文件阶段
举个例子来理解这三部分,我写了一个main.cpp文件,如下:
#include <iostream>
#include <cmath> // 包含数学库
int main() {
double result = sqrt(16.0); // 使用 libm 库中的 sqrt 函数
std::cout << "The square root of 16 is " << result << std::endl;
return 0;
}
1.编译阶段
在这个阶段,编译器将源代码(main.cpp
)编译成目标文件(.o
文件)。编译器会把源代码中的函数调用(sqrt()
)保留为一个符号引用(symbol reference),但并没有实际的函数代码。也就是说,编译器并不知道 sqrt()
的具体实现,它只是知道程序需要用到一个 sqrt
函数。
2.链接阶段:
在链接阶段,链接器(Linker)会将所有的目标文件(main.o
)以及所需的外部库(libm
)结合起来,解决这些符号引用(如 sqrt()
)。链接器通过查找外部库(比如 libm
)中的符号表来找到 sqrt()
函数的实现。
-
静态链接:如果使用的是静态库(例如
.a
文件),链接器会把sqrt()
函数的代码从静态库中提取出来,并把这段代码直接嵌入到目标文件(.o
文件)中。这时,main.o
中调用sqrt()
的地方会被替换成实际的sqrt()
函数实现代码。 -
动态链接:如果使用的是动态库(例如
.so
或.dll
文件),链接器并不把sqrt()
的代码直接嵌入到目标文件中,而是将一个“地址”或“符号表项”插入到目标文件中。程序在运行时,会通过这个符号表项来动态加载对应的共享库并调用sqrt()
函数。换句话说,程序运行时,链接器会在运行时去查找和加载对应的动态库,并解析sqrt()
的具体实现。
3.生成可执行文件:
如果是 静态链接,最终的可执行文件已经包含了 sqrt()
的实现代码,因此程序不再需要依赖外部的 libm
库文件,它已经将该库的相关部分直接嵌入到了可执行文件中。
如果是 动态链接,最终的可执行文件仍然只包含对 sqrt()
函数的调用地址(即符号引用),在程序运行时,操作系统会将动态库(如 libm.so
)加载到内存,并解析这个符号,从而实现函数的调用。
(个人理解,如有错误,恳请指正)