链接是将各种代码和数据部分收集起来并且组合成一个单一文件的过程,这个文件可以被加载到存储器并执行。
整个编译的过程是:预处理,编译,汇编,链接。链接后生成可执行的目标文件,汇编后生成的目标文件被链接器使用,结合成可执行的目标文件。
目标文件有三种形式
- 可重定位的目标文件:包含二进制代码和数据,其形式可以再编译时与其他可重定位的目标文件合并起来,创建一个可执行目标文件。通常对一个程序模块进行汇编以后,生成的就是可重定位的目标文件。
- 可执行目标文件:包含二进制代码和数据,可以直接拷贝到存储器并执行。
- 共享目标文件:可以再加载或者运行时被动态的加载到存储器并链接。
总结一下其实就是编译器和汇编器一起生成可重定位的目标文件,链接器生成可执行目标文件。从技术上说一个目标模块就是一个字节序列,一个目标文件就是存放在磁盘上的目标模块。
下面继续讲解一下静态库和动态库,以及区别。
首先我们说为什么系统要支持库这个概念呢?以ANSI C为例,它定义了一组广泛的标准I/O,字符串操作和数学操作等,比如函数atoi,printf。他们都在libc.a库中,对于每一个C程序来说都是可用的。
那么假设我们不适用静态库的话,如何向用户提供这些函数呢?第一种方法是让编译器辨认出对标准函数的调用,并且直接生成汇编代码。Pascal采用的就是这种方法,但是对于C来说不合适,因为C的标准函数太多了,这无形中是给编译器增加了复杂性。虽然这对于程序员来说会是非常方便,因为标准函数将总是可用的。另一种方法就是将每个标准C函数都写进一个单独的可重定位目标模块中,应用程序员可以把这些模块链接到他们的可执行文件中。这个方法的优点就是将编译器的实现与标准函数的实现分离开,并且对程序员来说保持了一定的便利性。但是一个很大的缺点就是系统中每个可执行文件现在都包含一份标准函数集合的完全拷贝,这对于磁盘空间是一种浪费。每个运行的程序都将自己的这些函数的拷贝放在内存中,这对内存也是浪费。另一个大缺点就是任何一个标准函数的改变,无论大小,都要重新编译整个源文件,这是一个非常耗时的工作。
于是静态库的概念提出来。相关的函数可以被编译为独立的目标模块,然后封装成一个单独的静态库。然后,应用程序通过命令行上指定单独的文件名字使用这些在库中定义的函数。比如C的标准库和数学库中的函数就可以通过以下方法在一个程序中使用:gcc main.c /usr/lib/libm.a /usr/lib.libc.a
在链接时,链接器将只拷贝被程序调用的目标模块,这就减少了可执行文件在磁盘和内存中的大小。
在UNIX系统中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘中。存档文件时一组连接起来的空重定位目标文件的集合。以后缀名.a标识。
静态库也有缺点,更新后的静态库,必须重新和程序进行连接。还有一个问题是,多个程序都用一个静态库的时候,在运行时,这个静态库的代码会被拷贝到每个程序的文本段中。这就造成了浪费。那么共享库(相当于dll)的出现致力于解决这些问题。首先共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。UNIX系统中共享库以.so后缀名。