c++的链接器(Linker)是如何工作的? (符号解析与重定位)
技术百科
裘德小鎮的故事
发布时间:2026-01-22
浏览: 次 链接器通过符号表和重定位信息找到函数和变量定义:先扫描所有.o文件建立全局符号哈希表,再对UNDEF符号查表绑定地址;若未找到则报undefined reference错误。
链接器怎么找到函数和变量的定义?
链接器本身不看源码,只处理 .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 位表示范围。
多个同名 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() 在不同文件里定义,只要名字一样,链接器就强行连上,运行时崩在栈错位或返回值解释错误——这种问题必须靠编译期检查(如头文件统一声明)来防,链接器不管。
# ai
# 的是
# 几个
# 如果你
# 多个
# 第一个
# 也会
# 绑定
# 填入
# 工具
# go
# c++
# int
# void
# 为什么
# 栈
# Static
# printf
# 封装
# 头文件
# 作用域
# extern
# Object
# const
# 全局变量
# undefined
# table
# 或未
# symbol
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Win11如何更改用户账户文件夹名称 Win11修
- 如何使用Golang包导出规则_控制函数和变量可见
- Win11怎么关闭内容自适应亮度_Windows1
- c# 在高并发场景下,委托和接口调用的性能对比
- Win10电脑怎么设置休眠快捷键_Windows1
- 如何使用Golang安装依赖库_管理模块和第三方包
- Win11如何设置电源计划_Win11电源计划优化
- Windows怎样关闭开始菜单广告_Windows
- Win11怎么关闭搜索历史 Win11清除搜索框最
- Win11无法识别耳机怎么办_解决Win11插耳机
- Win11资源管理器卡顿怎么办 Win11文件资源
- 如何使用Golang sync.Map实现并发安全
- Windows10如何更改鼠标图标_Win10鼠标
- 如何在Golang中实现服务熔断与限流_Golan
- VSC怎么创建PHP项目_从零开始搭建项目的步骤【
- Windows11怎样开启游戏模式_Windows
- Python与MongoDB NoSQL开发实战_
- c++中的CRTP是什么 c++奇异递归模板模式【
- 为什么本地php环境运行php脚本卡顿_php执行
- php订单日志怎么在swoole写_php协程sw
- Windows10如何更改系统字体大小_Win10
- Win11怎么格式化U盘_Win11系统U盘格式化
- Win11怎么关闭OneDrive同步_Win11
- c++ std::atomic如何保证原子性 c+
- Win10任务栏天气和资讯怎么关闭 Win10禁用
- PHP 中如何在函数内持久化修改引用变量的指向
- Win10系统更新错误0x80240034怎么办
- Windows10电脑怎么设置自动连接WiFi_W
- C++中的协变与逆变是什么?C++函数指针与返回类
- 如何在Golang中处理JSON字段缺失_Gola
- Windows怎样关闭开始菜单推荐广告_Windo
- Win11怎么更改文件夹图标_自定义Win11文件
- Win11玩游戏全屏闪退怎么办_Win11全屏优化
- Win11怎么设置默认浏览器Chrome_Wind
- Win10怎样设置闹钟贪睡时间 Win10闹钟贪睡
- Win11怎样安装剪映专业版_Win11安装剪映教
- Windows10无法连接到Internet_Wi
- Win11怎么解压RAR文件 Win11自带解压功
- 微信JSAPI支付回调PHP怎么接收_处理JSAP
- ACF 教程:正确更新嵌套在多层 Group 字段
- 如何在Golang中实现基础配置管理功能_Gola
- 如何在Golang中解压文件_Golang com
- Windows10电脑怎么设置虚拟光驱_Win10
- Python模块的__name__属性如何由导入方
- Windows10电脑怎么查看硬盘通电时间_Win
- Python面向对象实战讲解_类与设计模式深入理解
- 如何在 Python 测试中动态配置 @backo
- Win10如何卸载预装Edge扩展_Win10卸载
- mac本地php环境如何开启curl_curl扩展
- Win11怎么更改系统语言为中文_Windows1

QQ客服