C语言-链接

本文最后更新于:1 年前

链接器概念

​ 编译器和链接器的一项重要任务就是将助记符替换成地址。修改程序后,需要重新计算每个子程序或者跳转的目标地址,这个过程叫做重定位(Relocation)

​ 使用机器语言编程时每次修改代码后需要人工重新计算目标地址,直到汇编语言出现。汇编语言使用接近人类的各种符号和标记来帮助记忆,比如用jmp表示跳转指令,用func表示一个子程序(C语言中的函数就是一个子程序)的起始地址,这种符号的方法使得人们从具体的机器指令和二进制地址中解放出来。

符号(Symbol)这个概念随着汇编语言的普及被广泛接受,它用来表示一个地址,这个地址可能是一段子程序(后来发展为函数)的起始地址,也可以是一个变量的地址。

​ 在程序被分隔成多个模块后,需要解决的一个重要问题是如何将这些模块组合成一个单一的可执行程序。在C语言中,模块之间的依赖关系主要有两种:一种是模块间的函数调用,另外一种是模块间的变量访问。

在C语言中,一个模块可以认为是一个源文件(.c 文件)

​ 函数调用需要知道函数的首地址,变量访问需要知道变量的地址,所以这两种方式可以归结为一种,那就是模块间的符号引用。

​ 这种通过符号将多个模块拼接为一个独立的程序的过程就叫做链接(Linking)。从原理上来讲,链接器的作用就是找到符号(函数、全局变量,忽略局部变量)的地址,或者把指令中使用到的地址加以修正。这个过程称为符号决议(Symbol Resolution)或者重定位(Relocation)

每个需要被修正的地方叫做一个重定位入口(Relocation Entry)

链接步骤

1.符号决议(Symbol Resolution)

当要进行链接时,链接器首先扫描所有的目标文件,获得各个段的长度、属性、位置等信息,并将目标文件中的所有(符号表中的)符号收集起来,统一放到一个全局符号表。

在这一步中,链接器会将目标文件中的各个段合并到可执行文件,并计算出合并后的各个段的长度、位置、虚拟地址等。

在目标文件的符号表中,保存了各个符号在段内的偏移,生成可执行文件后,原来各个段(Section)起始位置的虚拟地址就确定了下来,这样,使用起始地址加上偏移量就能够得到符号的地址(在进程中的虚拟地址)。

这种计算符号地址的过程被称为符号决议(Symbol Resolution)

2. 重定位

重定位表.rel.text和.rel.data中保存了需要重定位的全局符号以及重定位入口,完成了符号决议,链接器会根据重定位表调整代码中的地址,使它指向正确的内存位置。(修改.text段和.data段中对每个符号的引用(地址))

至此,可执行文件就生成了,链接器完成了它的使命。

强符号和弱符号

在C语言中,编译器默认函数和初始化了的全局变量为强符号(Strong Symbol),未初始化的全局变量为弱符号(Weak Symbol)

注意,不包括函数内的局部变量,和加static修饰的本地符号

  1. 不允许强符号被多次定义,也即不同的目标文件中不能有同名的强符号;如果有多个强符号,那么链接器会报符号重复定义(Multiple Definition)错误。

  2. 如果一个符号在某个目标文件中是强符号,在其他文件中是弱符号,那么选择强符号。

  3. 如果一个符号在所有的目标文件中都是弱符号,那么选择其中占用空间最大的一个。

强引用和弱引用

在变量声明或函数声明的前面加上__attribute__((weak))就会使符号变为弱引用

强引用:

在所有目标文件被链接成可执行文件时,它们的地址都要被找到,如果没有符号定义,链接器就会报符号未定义错误

弱引用:

如果符号有定义,就使用它对应的地址,如果没有定义,也不报错

对于未定义的弱引用,链接器不认为它是一个错误,一般默认其为 0(地址为 0),或者是一个特殊的值,以便程序代码能够识别。