CSAPP 第七章:Linking

链接的作用 (What is Linking)

  • 链接 (Linking):把多个 目标文件 (.o)库文件 (.a / .so) 组合成一个单独的 可执行文件

  • 链接既可以在编译时完成,也可以在运行时动态完成。

链接过程

  1. 编译器 (gcc -c)

    • C 源代码 .c → 汇编器生成 .o
  2. 静态链接器 (ld)

    • 把多个 .o 文件和库文件合并成一个可执行程序
  3. 动态链接

    • 程序运行时,由 动态链接器 (ld-linux.so) 负责加载动态库

📌 关键作用

  • 符号解析 (Symbol Resolution):把函数和全局变量的引用匹配到它们的定义。

  • 重定位 (Relocation):确定每个符号的最终内存地址,并修改代码/数据里的地址引用。

静态链接 (Static Linking)

  • 静态链接器 (ld) 输入:

    • 目标文件 (.o)

    • 静态库 (.a) → 一组目标文件的集合

  • 输出

    • 单个 可执行文件 (ELF),里面包含所有需要的代码和数据

示例

1
gcc -static main.o sum.o -o prog

📌 特点:

  • 生成的可执行文件大,但运行时不依赖外部库。

  • 库函数的代码被完整复制进可执行文件。

动态链接 (Dynamic Linking)

  • 动态库 (.so) 在程序运行时才装载。

  • 动态链接器 (ld-linux.so) 会:

    • 加载共享库到内存

    • 进行符号解析和重定位

示例

1
gcc main.o -lsum -o prog

📌 特点:

  • 节省空间(多个进程共享一份库代码)。

  • 更新库文件不需要重新编译程序。

ELF 文件结构 (Executable and Linkable Format)

  • ELF 可执行文件/目标文件的组成
作用
.text 机器指令
.rodata 只读常量(字符串字面量)
.data 已初始化的全局/静态变量
.bss 未初始化的全局/静态变量(运行时清零)
.symtab 符号表(函数/变量的定义与引用)
.rel.text 代码段里的重定位信息
.rel.data 数据段里的重定位信息
.debug 调试信息 (仅调试用)

📌 未初始化的全局变量不会占用文件空间,只在运行时分配。

符号解析 (Symbol Resolution)

  • 每个符号 (symbol) 对应一个函数或全局变量。

  • 符号类型

    • 全局符号:在一个模块中定义,可以被其他模块引用

    • 外部符号:在本模块中引用,在别处定义

    • 局部符号:只在本模块内部使用

例子

1
2
3
4
// main.c
extern void f(); // 外部符号
int x = 1; // 全局符号
int main() { f(); }
1
2
3
// f.c
int x = 2; // 另一个全局符号
void f() { }

⚠️ 冲突规则 (Strong/Weak Symbols)

  • 强符号 (Strong):已初始化的全局变量、函数

  • 弱符号 (Weak):未初始化的全局变量

  • 规则:

    1. 不允许出现多个强符号同名(链接报错)

    2. 强符号覆盖弱符号

    3. 如果只有弱符号,任选一个

重定位 (Relocation)

  • 目标文件的代码和数据段只是相对地址,必须在链接时修正。

  • 两类重定位

    1. 重定位节和符号定义:把所有目标文件的节合并,确定每个符号的虚拟地址

    2. 修正代码和数据里的引用:把指令/数据中对符号的引用替换成实际地址

例子:函数调用

1
2
3
4
extern void f();
int main() {
f(); // 编译时不知道 f 的地址
}

汇编中 call f → 链接时在 .rel.text 中有一个重定位项,指向 f 的地址。

静态库 (Static Libraries)

  • 归档文件 (.a):由 ar 工具打包多个 .o 文件
1
ar rcs libfoo.a foo1.o foo2.o
  • 使用:
1
gcc main.o -L. -lfoo -o prog

📌 链接器只会提取库中 被引用到的目标文件

动态库 (Shared Libraries)

  • 创建动态库
1
gcc -fPIC -shared -o libfoo.so foo.c
  • 使用动态库
1
gcc main.o -L. -lfoo -o prog

运行时 ld-linux.so 会在:

  • /lib /usr/lib

  • LD_LIBRARY_PATH
    找到 libfoo.so

延迟绑定 (Lazy Binding)

  • GOT (全局偏移表) + PLT (过程链接表) 实现动态函数调用。

执行流程:

  1. 程序第一次调用库函数 printf

    • 跳到 PLT 表项

    • 触发 ld-linux.so 查找真正地址

    • 写入 GOT

  2. 之后的调用直接通过 GOT 跳转

📌 优点:加快程序启动,函数地址在第一次调用时才解析。

静态 vs 动态链接对比

特性 静态链接 动态链接
可执行文件大小
运行时依赖 需要共享库
更新库 需重新编译 可直接替换库
加载速度 首次调用需解析符号

CSAPP 第七章:Linking
http://example.com/2025/09/05/csapp-linking/
Author
Newtown
Posted on
September 5, 2025
Licensed under