0

0

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

裘德小鎮的故事

裘德小鎮的故事

发布时间:2026-01-22 17:42:59

|

267人浏览过

|

来源于php中文网

原创

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

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

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

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

符号表里记录了所有 externglobalstatic(带本地作用域的)符号,每条记录含名称、类型(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)、要填谁的地址(符号名)。链接器在重定位阶段,根据符号最终地址和当前指令位置,算出相对偏移,写回机器码。

立即学习C++免费学习笔记(深入)”;

典型重定位类型差异:

  • 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 位表示范围。

灵光
灵光

蚂蚁集团推出的全模态AI助手

下载

多个同名 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() 在不同文件里定义,只要名字一样,链接器就强行连上,运行时崩在错位或返回值解释错误——这种问题必须靠编译期检查(如头文件统一声明)来防,链接器不管。

相关专题

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

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

73

2023.06.20

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

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

282

2023.11.28

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

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

526

2023.09.20

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

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

78

2025.09.18

python 全局变量
python 全局变量

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

96

2025.09.18

string转int
string转int

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

338

2023.08.02

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

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

542

2024.08.29

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

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

53

2025.08.29

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

9

2026.01.22

热门下载

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

精品课程

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

共32课时 | 4万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号