链接
编译系统
以下是一个 hello.c 程序:
1 |
|
在 Unix 系统上,由编译器把源文件转换为目标文件。
1 | gcc -o hello hello.c |
这个过程大致如下:
- 预处理阶段:处理以 # 开头的预处理命令,例如解决头文件的包含关系,宏定义替换,条件编译处理,去掉注释等;
- 编译阶段:检查函数和变量是否存在声明,检查语法是否正确,将C文件翻译成汇编文件;
- 编译优化 -O1 -O2 -O3(利用了cpu流水线和缓存功能等) Og
- 汇编阶段:将 .s 汇编文件翻译成可重定位 .o 目标文件,二进制机器指令文件;
- 链接阶段:将所有的 .o 文件 还有库文件连接生成可执行文件。编译器将每个函数变量都使用符号表示,然后为每个目标文件提供两个符号表用于和其他文件连接。
链接
链接过程分为两步:重定位,符号解析
重定位:由于多个编译单元 .o 文件的符号地址可能相同,因此重定位就是在连接时会对每个编译单元中的符号地址进行调整,具体的文件内的地址加上该文件最终在可执行文件中的偏移位置即为重定位之后的内存地址。
符号解析 在符号解析阶段,编译器会给每个.o (编译单元)文件生成两个符号表
- 未解决符号表:就是本单元声明但是没有定义的符号
- 导出符号表:就是本单元定义,并且可供给其他文件调用的符号表
连接器会根据目标文件提供的未解决的符号表去所有的编译单元的导出符号表中查找与这个匹配的符号名,如果找到就将这个符号地址填到未解决的符号的地址处。
最后把所有的目标文件的内容写在各自的位置上,就生成一个可执行文件。
静态链接
静态链接在编译阶段就将库文件的所有代码加到可执行文件中,因此生成的程序体积更大,其后缀名一般为 .a
静态链接器以一组可重定位目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
动态链接
静态库有以下两个问题:
- 当静态库更新时那么整个程序都要重新进行链接;
- 对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。
共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示,Windows 系统上它们被称为 DLL。动态链接在编译链接时并不会把库文件的代码加到可执行文件中,而是在运行时加载所需的动态库,后缀名一般为 .so
- 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中;
- 在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。