类成员函数编译链接的过程
1.静态成员函数和普通成员函数
源文件编译成目标文件,静态成员函数和普通成员函数在目标文件代码段,函数添加进了符号表,地址是在代码段的相对地址,这个地址只是一个临时地址因为后面链接时还要合并代码段,函数地址还要变,然后链接合并目标文件,代码段合并后静态成员函数和普通成员函数固定了,修改符号表中的函数地址,最后靠重定位表修改代码段和数据段里函数调用的地址,就完了
2.虚函数
源文件编译成目标文件,虚函数在目标文件代码段,并且虚函数添加进了符号表,地址是在代码段的相对地址,这个地址只是一个临时地址因为后面链接时还要合并代码段,函数地址还要变,虚函数表创建好在只读数据区,函数地址和符号表一样是临时地址。然后链接合并目标文件,代码段合并后虚函数固定了,修改符号表中的函数地址,修改虚函数表里的函数地址,只读数据段合并后虚函数表固定了,向构造函数中添加虚函数表指针的硬编码指令,最后靠重定位表修改代码段和数据段里函数调用的地址,就完了
ps:虚基表过程同虚函数表,不过虚基表的内容在编译时就已经确定,只需要在链接合并只读数据段虚基表固定了,往构造函数添加虚基表指针的硬编码即可
下面是AI生成的理解
1. 重定位的核心作用
重定位 不修改符号表,而是修正 代码段(.text)和数据段(.data)中对符号的引用地址。符号表的更新是链接过程的副产品,而非重定位的直接目标。
(1) 符号表的角色
-
编译阶段:记录符号名称、类型(函数/变量)和 临时地址(目标文件内部的偏移量)。
-
链接阶段:链接器分配最终地址后,符号表中的地址会被更新,但 符号表本身不参与运行时执行。
(2) 重定位的真实行为
-
修正目标:代码段中的
call
指令、数据段中的指针等 对符号的引用。 -
操作方式:根据重定位表(
.rel.text
/.rel.data
)的指示,将临时占位符替换为最终地址。
2. 具体流程示例
假设有两个文件:
// a.cpp void foo(); // 声明 int main() { foo(); } // 编译为:call 0x00000000(需重定位)// b.cpp void foo() {} // 定义(临时地址0x00000010)
(1) 编译后目标文件的状态
-
a.o 的符号表:
_Z3foov: UND (未定义) // 声明 main: T (定义在.text段) // 地址 0x00000000
-
a.o 的重定位表:
OFFSET=0x00000005, TYPE=R_X86_64_PC32, SYMBOL=_Z3foov
(
call
指令的操作数位于main
函数偏移0x05
处)
(2) 链接时的操作
-
合并段:将
a.o
和b.o
的.text
段合并,假设:-
main
函数最终地址:0x401000
-
foo
函数最终地址:0x401020
-
-
重定位修正:
-
找到
a.o
中call
指令的位置(0x401005
)。 -
计算相对偏移:
0x401020 (foo) - 0x40100A (下条指令) = 0x16
。 -
将
call 0x00000000
修正为call 0x16
(实际编码为E8 16 00 00 00
)。
-
-
符号表更新(副产品):
-
_Z3foov
的地址更新为0x401020
(仅用于调试,不参与运行)。
-
3. 关键区别:符号表 vs. 重定位
目标文件组件 | 作用 | 是否影响运行时 |
---|---|---|
符号表(.symtab) | 记录符号名称和地址的映射 | 否(调试用) |
重定位表(.rel.*) | 标记需要修正的指令/数据位置 | 是(直接修改代码) |
-
符号表:像一本书的目录,帮助链接器找到符号定义。
-
重定位表:像编辑批注,告诉链接器哪些地方需要修改。