1. 链接的目的
源代码经过编译后,生成了一系列的目标文件。每一个目标文件,具有自己的代码段、数据段、符号表、重定位表等信息(参考目标文件结构介绍)。
在代码段中,编译生成的机器代码需要访问数据段的数据,或是代码段的函数。但在单个目标文件中,只有局部的数据和代码信息。如下例:
文件a.c:
unsigned long a = 10;
void main()
{
print_a();
}
文件b.c:
#include <stdio.h>
extern unsigned long a;
void print_a()
{
printf(“%d”,a);
}
编译后,a.c和b.c分别生成了a.o和b.o文件。在a.o中,数据段中为a,代码段中有main;在b.o中,代码段中有print_a函数。
a.o中,访问了b.o中的print_a函数,而b.o中访问了a.o中的数据a。但问题是每一个.o都只能知道自已的局部数据和函数的地址,因此就需要通过链接过程,对a.o和b.o中的指令(调用print_a函数及访问a数据)进行重点位。这也是静态链接的主要目的所在。
2. 链接的过程
链接主要分为两个步骤以:
1) 空间与地址分配
每个.o文件中都有自己的代码段和数据段,在多个文件合并后,这些指令和数据如何组织?通常是采用相似段合并的方式,将多个.o文件中的代码段合并为一个代码段,数据段合并为一个数据段。
这样做的好处是可以空间消耗(因为操作系统在分配空间时最小单位是页),并且可以提高内存访问的命中率。
在进行相似段合并时,链接器会根据.o中每个段的长度、属性和位置等,计算出合并后的长度与位置,然后并每一个.o的中的符号合并到全局符号表,记录每一个符号的新的位置。
2) 符号解析与重定位
这一步是链接过程中核心,链接器通过重定位表,获取到每一个需要进行重定位的指令的位置以及符号,然后通过符号表查询符号的地址,修改指令中访问的地址。
如果在这个过程中,无法在符号表中找到需要重定位的符号,就会出现链接失败,也就是我们常见的”undefined reference to ‘xxx’”错误。
在符号表中,除了全局符号外,COMMON类型的符号也需要进行重定位,又称为COMMON块。COMMON块通常用来处理弱符号。
编译器将未初始化的全局变量定义为弱符号。当多个源代码文件中存在相同的弱符号时,链接器采用如下策略决定弱符号的大小:
(1) 存在同名的强符号时,以强符号为准
(2) 不存在同名强符号时,以最大的弱符号为准