c++的链接器(Linker)是如何工作的? (符号解析与重定位)

陌墨同学_2224

陌墨同学_2224

2026-01-22

327人浏览

原创

链接器通过符号表和重定位信息找到函数和变量定义:先扫描所有.o文件建立全局符号哈希表,再对UNDEF符号查表绑定地址;若未找到则报undefined reference错误。

c++的链接器(linker)是如何工作的? (符号解析与重定位)

链接器怎么找到函数和变量的定义?

链接器本身不看源码,只处理 .o(目标文件)里的符号表和重定位信息。每个 .o 文件在编译后都包含三类关键内容:.text(机器码)、.data(已初始化数据)、.bss(未初始化数据),以及两个核心元数据:symbol table(符号表)和 .rela.text/.rela.data(重定位表)。

符号表里记录了所有 extern、global、static(带本地作用域的)符号,每条记录含名称、类型(FUNC/OBJECT)、绑定(GLOBAL/LOCAL)、大小、所在节区偏移。链接器第一遍扫描所有输入 .o,把 GLOBAL 符号按名称建哈希表;第二遍再扫,对每个 UNDEF 符号(比如调用的 printf 或未定义的 foo()),查这个表——查到就绑定地址,查不到就报 undefined reference 错误。

常见卡点:

  • static 函数/变量不会进入全局符号表,所以其他 .o 文件无法引用——不是链接失败,是根本“看不见”
  • 函数声明但没定义(比如只写了 void bar(); 没实现),编译能过,链接必报 undefined reference to 'bar'
  • C++ 的名字修饰(name mangling)会让 void foo(int) 变成类似 _Z3fooi 的符号名;如果头文件声明和实现文件签名不一致(比如一个写 const int&,另一个写 int&),符号对不上,链接器就找不到定义

为什么调用 printf 时地址还是 0?

因为调用指令(如 x86-64 的 call)在目标文件里填的是占位地址——通常是 0x0 或某个预留值。这个位置被标记在 .rela.text 表中,记录了:要修改哪条指令(偏移)、改哪个字段(R_X86_64_PLT32 还是 R_X86_64_PC32)、要填谁的地址(符号名)。链接器在重定位阶段,根据符号最终地址和当前指令位置,算出相对偏移,写回机器码。

典型重定位类型差异:

  • R_X86_64_PC32:用于直接调用同模块函数,填入「目标地址 - 当前指令下一条地址」的 32 位补码
  • R_X86_64_PLT32:用于调用外部函数(如 printf),填入 PLT 表项地址,靠 PLT + GOT 间接跳转
  • R_X86_64_64:用于全局变量取地址(&g_var),直接填绝对地址——这会导致代码不可重定位(PIE 禁用)

如果你看到 relocation truncated to fit 错误,大概率是用了 R_X86_64_64 去填一个本该用 PC-relative 的地方,或者符号地址超出了 32 位表示范围。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载

多个同名 global 符号会怎样?

链接器默认按“强弱符号”规则处理:函数和已初始化变量是强符号,未初始化变量(int g;)是弱符号。规则是:多个强符号 → 链接错误;一个强 + 多个弱 → 用强的;多个弱 → 任选一个(通常第一个)。

这导致几个经典陷阱:

  • 头文件里写 int global = 42; 并被多个 .cpp 包含 → 每个 .o 都生成一个强符号 → 链接时报 multiple definition
  • 想用弱符号做“可覆盖默认实现”,得显式加 __attribute__((weak)),否则普通 static 或未初始化变量不满足预期行为
  • inline 函数在多个单元定义,靠编译器保证符号弱化或内联消除;但若编译器没内联,又没加 inline 声明,也会触发多重定义

如何用工具看符号和重定位细节?

别猜,直接用系统工具看真实数据:

nm -C main.o          # 查看符号(-C 解析 C++ 名字)
readelf -s lib.o       # 更详细的符号表(含绑定、类型)
readelf -r main.o      # 查看重定位入口
objdump -d main.o      # 反汇编,看 call 指令后跟着的占位地址

特别注意 nm 输出第一列:大写 T 是强定义函数,小写 t 是局部函数;U 是未定义,B/b 是 bss 段变量。如果发现本该定义的符号显示为 U,说明编译时没把它打进去——可能文件根本没参与链接,或者被 static 封装了。

真正容易被忽略的是:链接器不验证类型一致性。它只认名字。哪怕 void f() 和 int f() 在不同文件里定义,只要名字一样,链接器就强行连上,运行时崩在栈错位或返回值解释错误——这种问题必须靠编译期检查(如头文件统一声明)来防,链接器不管。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关专题

更多
printf用法大全
printf用法大全

php中文网为大家提供printf用法大全,以及其他printf函数的相关文章、相关下载资源以及各种相关课程,供大家免费下载体验。

2023.06.20

868

5

fprintf和printf的区别
fprintf和printf的区别

fprintf和printf的区别在于输出的目标不同,printf输出到标准输出流,而fprintf输出到指定的文件流。根据需要选择合适的函数来进行输出操作。更多关于fprintf和printf的相关文章详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.28

1029

10

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2023.09.20

2338

7

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

2025.09.18

4945

12

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

2025.09.18

2090

13

c++ 全局变量
c++ 全局变量

本专题整合了c++全局变量的使用、定义、作用域等等内容,阅读专题下面的文章了解更多详细内容。

2026.03.17

199

17

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

6959

3

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.29

3345

6

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

4348

10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程