链接
编译系统
以下是一个 hello.c 程序:
1 |
|
在 Unix 系统上,由编译器把源文件转换为目标文件。
1 | gcc -o hello hello.c |
这个过程大致如下:
- 预处理阶段:处理以 # 开头的预处理命令
- 编译阶段:翻译成汇编文件。
- 汇编阶段:将汇编文件翻译成可重定位目标文件。
- 链接阶段:将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件。
静态链接
为什么要进行静态链接?
在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个 *.c 文件会形成一个 *.o 文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接。
静态链接的执行
静态链接器以一组可重定位目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
- 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
- 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
静态链接的优点
在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
静态链接的缺点
- 浪费空间。因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了 printf() 函数,则这多个程序中都含有 printf.o,所以同一个目标文件都在内存存在多个副本。
- 更新困难。因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
动态链接
为什么会出现动态链接?
动态链接出现的原因就是为了解决静态链接中提到的空间浪费和更新困难。
动态链接的执行
共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 * .so 来表示,Windows 系统上它们被称为 DLL。它具有以下特点:
- 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中;
- 在内存中,一个共享库的 *.text (已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。
动态链接的优点
- 即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多份副本,而是这多个程序在执行时共享同一份副本。
- 更新方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。
动态链接的缺点
把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
静态链接与动态链接的区别
静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时。
目标文件
- 可执行目标文件:可以直接在内存中执行。
- 可重定位目标文件:可与其它可重定位目标文件在链接阶段合并,创建一个可执行目标文件。
- 共享目标文件:这是一种特殊的可重定位目标文件,可以在运行时被动态加载进内存并链接。