深入理解计算机系统--链接

引言

链接是将各种代码和数据收集起来并组合成一个单一文件的过程,这个文件可被加载到存储器并执行。
现代系统中,链接是由链接器(linker)自动执行的。

符号解析

首先定义强符号与弱符号。

  • 强符号:函数和已初始化的全局变量。
  • 弱符号:未初始化的全局变量。

规则1

不允许有多个强符号。

规则2

如果有一个强符号和多个弱符号,那么选择强符号。
如果在一个模块里x未被初始化,那么链接器将安静地选择定义在另一个模块中的强符号。

规则3

如果有多个弱符号,那么从这些弱符号中任意选择一个。

一个有趣的错误

在foo5模块中,x被定义为int类型;在bar5模块,x被定义为double类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//foo5.c
#include <stdio.h>
void f(void);

int x = 15213;
int y = 15212;

int main()
{
f();
printf("x = 0x%x, y = 0x%x\n", x, y);
return 0;
}

//bar5.c
double x;

void f() {
x = -0.0;
}

使用gcc(版本为4.8.0)编译,会有警告信息。与参考文献中所说的没有警告信息有出入。

1
2
$gcc -o foobar5 foo5.c  bar5.c 
$/usr/bin/ld: Warning: alignment 4 of symbol `x' in /tmp/ccrGpxXb.o is smaller than 8 in /tmp/ccYL3Zqs.o

运行结果:

1
2
$./foobar5
$x = 0x0, y = 0x80000000

结果显示,y的值被修改,这是因为double类型是8个字节,int类型是4个字节,在bar5模块中对x赋值,双精度浮点数覆盖了存储器中x和y的位置。

重定位

链接器完成符号解析之后,就可以把代码中的每个符号引用和确定的一个符号定义联系起来。此时,链接器就知道它的输入目标模块中的代码节和数据节的确切大小,就可以开始重定位了。
重定位由两步组成:

  1. 重定位节和符号定义。链接器将所有相同类型的节合并,得到新的聚合节。
  2. 重定位节中的符号定义。链接器修改代码节和数据节中对每个符号的引用,使他们指向正确的地址。

动态链接共享库

静态库的缺点

  1. 需要定期维护和更新,如果程序员想使用最新版本,必须了解该库的更新情况,显式地将程序与更新的库重新链接。
  2. 库中被调用函数的代码会被复制到每个运行进程的代码段中,几乎每个程序都会包含标准I/O函数,这对存储资源是一种浪费。

共享库是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行时可以加载到任意的存储器地址,并和一个在存储器的程序链接起来。这个过程称为动态链接,由动态链接器的程序来执行。

共享库也称为共享目标(shared object)。在Unix、Linux系统中通常用后缀.so来表示。Window系统也大量利用了共享库,称为动态链接库,用后缀.dll来表示。

参考文献

  • (美)布莱恩特(Bryant, R. E.), (美)奥哈拉伦(O’Hallaron),等. 深入理解计算机系统[M]. 机械工业出版社, 2012.