CSAPP 第七章:Linking
链接的作用 (What is Linking)
链接 (Linking):把多个 目标文件 (.o) 和 库文件 (.a / .so) 组合成一个单独的 可执行文件。
链接既可以在编译时完成,也可以在运行时动态完成。
链接过程
编译器 (gcc -c)
- C 源代码
.c
→ 汇编器生成.o
- C 源代码
静态链接器 (ld)
- 把多个
.o
文件和库文件合并成一个可执行程序
- 把多个
动态链接
- 程序运行时,由 动态链接器 (ld-linux.so) 负责加载动态库
📌 关键作用:
符号解析 (Symbol Resolution):把函数和全局变量的引用匹配到它们的定义。
重定位 (Relocation):确定每个符号的最终内存地址,并修改代码/数据里的地址引用。
静态链接 (Static Linking)
静态链接器 (ld) 输入:
目标文件 (.o)
静态库 (.a) → 一组目标文件的集合
输出:
- 单个 可执行文件 (ELF),里面包含所有需要的代码和数据
示例
1 |
|
📌 特点:
生成的可执行文件大,但运行时不依赖外部库。
库函数的代码被完整复制进可执行文件。
动态链接 (Dynamic Linking)
动态库 (.so) 在程序运行时才装载。
动态链接器 (ld-linux.so) 会:
加载共享库到内存
进行符号解析和重定位
示例
1 |
|
📌 特点:
节省空间(多个进程共享一份库代码)。
更新库文件不需要重新编译程序。
ELF 文件结构 (Executable and Linkable Format)
- ELF 可执行文件/目标文件的组成:
段 | 作用 |
---|---|
.text |
机器指令 |
.rodata |
只读常量(字符串字面量) |
.data |
已初始化的全局/静态变量 |
.bss |
未初始化的全局/静态变量(运行时清零) |
.symtab |
符号表(函数/变量的定义与引用) |
.rel.text |
代码段里的重定位信息 |
.rel.data |
数据段里的重定位信息 |
.debug |
调试信息 (仅调试用) |
📌 未初始化的全局变量不会占用文件空间,只在运行时分配。
符号解析 (Symbol Resolution)
每个符号 (symbol) 对应一个函数或全局变量。
符号类型:
全局符号:在一个模块中定义,可以被其他模块引用
外部符号:在本模块中引用,在别处定义
局部符号:只在本模块内部使用
例子
1 |
|
1 |
|
⚠️ 冲突规则 (Strong/Weak Symbols):
强符号 (Strong):已初始化的全局变量、函数
弱符号 (Weak):未初始化的全局变量
规则:
不允许出现多个强符号同名(链接报错)
强符号覆盖弱符号
如果只有弱符号,任选一个
重定位 (Relocation)
目标文件的代码和数据段只是相对地址,必须在链接时修正。
两类重定位:
重定位节和符号定义:把所有目标文件的节合并,确定每个符号的虚拟地址
修正代码和数据里的引用:把指令/数据中对符号的引用替换成实际地址
例子:函数调用
1 |
|
汇编中 call f
→ 链接时在 .rel.text
中有一个重定位项,指向 f
的地址。
静态库 (Static Libraries)
- 归档文件 (.a):由
ar
工具打包多个.o
文件
1 |
|
- 使用:
1 |
|
📌 链接器只会提取库中 被引用到的目标文件。
动态库 (Shared Libraries)
- 创建动态库
1 |
|
- 使用动态库
1 |
|
运行时 ld-linux.so
会在:
/lib
/usr/lib
LD_LIBRARY_PATH
找到libfoo.so
延迟绑定 (Lazy Binding)
- GOT (全局偏移表) + PLT (过程链接表) 实现动态函数调用。
执行流程:
程序第一次调用库函数
printf
跳到
PLT
表项触发
ld-linux.so
查找真正地址写入
GOT
之后的调用直接通过
GOT
跳转
📌 优点:加快程序启动,函数地址在第一次调用时才解析。
静态 vs 动态链接对比
特性 | 静态链接 | 动态链接 |
---|---|---|
可执行文件大小 | 大 | 小 |
运行时依赖 | 无 | 需要共享库 |
更新库 | 需重新编译 | 可直接替换库 |
加载速度 | 快 | 首次调用需解析符号 |