链接器Linker)是把多个目标文件和库组合成可执行文件、动态库或新的可重定位对象的工具,核心职责是符号解析、库成员选择、重定位和输出布局。

核心问题

编译器单独编译一个翻译单元时,很多事情还不能最终决定:

  • 外部函数或变量在哪里定义。
  • 函数和全局变量最终放在哪个地址。
  • 静态库里哪些目标文件真的要放进程序。
  • 动态库导入表、导出表和运行时加载信息如何组织。

链接器把这些分散的二进制片段拼成一个能被操作系统加载的整体。

核心对象

对象作用
input object files编译输出的 .o / .obj 文件。
static libraries.a / .lib 归档,里面包含多个目标文件。
shared libraries.so / .dll / .dylib,用于动态链接或记录运行时依赖。
symbol table全局符号定义和引用集合。
relocation entries等最终地址确定后要修正的位置。
linker script / options控制内存布局、入口点、section 放置和导出符号。
linker plugin让链接器和编译器扩展协作,例如 LTO(链接时优化)

核心机制

链接器的简化流程:

  1. 读取命令行中的目标文件和库。
  2. 建立全局符号表,记录每个符号的定义方和引用方。
  3. 对静态库按规则抽取需要的成员。传统 Unix 链接器对库的扫描顺序敏感,因此 -lA -lB-lB -lA 可能结果不同。
  4. 检查未定义符号、重复定义、弱符号替换和 ABI 不兼容问题。
  5. 决定 section 布局和地址,执行重定位。
  6. 写出最终 ELF、PE、Mach-O 等格式文件。

重定位可以用一个简化公式表示:

patched_value = S + A - P

这里 S 是被引用符号的最终地址,A 是目标文件里记录的附加常量,P 是需要被修正的位置地址。不同平台和重定位类型公式会不同,但核心都是“等最终布局确定后,把占位引用改成正确地址或偏移”。

LTO 中的链接器

LTO(链接时优化) 中,链接器不只是最后拼文件。它还提供关键事实:

  • 哪些符号被非 LTO 对象引用,必须保留。
  • 哪些符号会导出给动态链接、插件或外部程序。
  • 哪些静态库成员需要被抽取并参与优化。
  • 哪些对象包含 LTO IR,需要交给 linker plugin 或 libLTO 处理。

LLVM 的 LTO 设计中,链接器先做符号解析,再把必须保留的符号和 bitcode 模块交给 LTO 优化器;优化器输出新的本地对象后,链接器继续完成普通链接。

边界与常见坑

  • 链接器不是编译器前端。它通常不解析 C/C++ 源码,也不负责类型检查。
  • 链接器错误不一定是链接器本身的错。未定义符号、重复定义、ABI 不一致和库顺序错误都可能来自构建配置。
  • 动态链接和静态链接的可见性、搜索路径和运行时加载规则不同,不能只看编译命令。
  • LTO 会让链接阶段变重,因为一部分优化和代码生成被推迟到链接时。
  • 自定义 linker script、裸机固件和嵌入式地址布局会让链接器成为系统正确性的关键边界。

相关术语