第五章 对链接的思考
5.1 函数库、链接和载入
绝大多数的编译器通常由多达六七个稍小的程序所组成,这些程序由一个叫做“编译器驱动器(compiler driver)的控制程序来调用。这些可以方便地从编译器中分离出来的单独程序包括:预处理器(preprocessor)、语法和语义检查器(syntactic and semantic checker)、代码生成器(code generator)、汇编程序(assembler)、优化器(optimizer)、链接器(linker),当然还包括一个调用所有这些程序并向各个程序传递正确选项的驱动器程序(driver program),优化器几乎可以加在上述所有阶段的后面。
目标文件并不能直接执行,它需要载入到链接器中。链接器确定main函数为初始进入点(程序开始执行的地方),把符号引用绑定到内存地址,把所有的目标文件集中在一起,再加上库文件,从而产生可执行文件。
用于PC的链接机制与那些用于更大系统的链接机制有着巨大的差别。PC的链接器一般只提供几个基本的I/O服务,就是被称作BIOS的程序。它们存在于内存中固定的地点,并不是每个可执行文件的一部分。如果PC程序或程序套件需要更高级的服务,可以通过库函数提供,但编译器必须把库函数链接到每个可执行文件中。
动态链接允许系统提供一个庞大的函数库集合,可以提供有用的服务。但是,程序将在运行时寻找它们,而不是把这些函数的二进制代码作为自身可执行文件的一部分。
如果函数库的一份拷贝是可执行文件的物理组成部分,那么我们称之为静态链接;如果可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库,那么我们称之为动态链接。
收集模块准备执行的三个阶段的规范名称是链接-编辑(link-editing)、载入(loading)和运行时链接(runtime linking)。
静态链接的模块被链接编辑并载入以便运行。
动态链接的模块链接编辑后载入,并在运行时进行链接以便运行。
程序执行时,在main() 函数被调用前,运行时载入器把共享的数据对象载入到进程地址空间。外部函数被真正调用前,运行时载入器并不解析它们。所以即使链接了函数库,如果没有实际调用,也不会带来额外开销。
5.2 动态链接的优点
优点是可执行文件的体积可以非常小。虽然运行速度稍慢一些,但是动态链接能够更加有效地利用磁盘空间,而且链接-编辑阶段的时间也会缩短(因为链接器的有些工作被推迟到载入时)。
动态链接的主要目的是把程序与它们使用的特定的函数库版本分离出来。取而代之的是,约定由系统向程序提供一个接口,该接口保持稳定,不随时间和操作系统的后续版本发生变化。
由于它介于应用程序和函数库二进制可执行文件所提供的服务之间的接口,所以称它为应用程序二进制接口(Application Binary Interface, ABI)。
尽管单个可执行文件的启动速度稍受影响,但动态链接可以从两个方面提高性能:
1. 动态链接可执行文件比功能相同的静态链接可执行文件的体积小。
2. 所有动态链接到某个特定函数库的可执行文件在运行时共享该函数库的一个单独拷贝。如果函数库是静态链接的,每个文件都将拥有一份函数库的拷贝,显然极为浪费。
动态链接使得函数库的版本升级更为容易。
动态链接允许用户在运行时选择需要执行的函数库。
与位置无关的代码表示用这种方法产生的代码保证对于任何全局数据的访问都是通过额外的间接方法完成的。这使它很容易对数据进行重新定位,只要简单地修改全局偏移量表的其中一个值就可以了。
在缺省情况下,编译器并不产生与位置无关的代码,因为额外的指针解除引用操作将使程序在运行时稍微变慢。
纯代码。纯可执行文件是只包含代码(无静态或初始化过的数据)的文件。它之所以成为“纯”是因为它不必进行修改就能被其他特定的进程执行。它从堆栈或者其他(非纯)段引用数据。纯代码段可以被共享。如果生成与位置无关代码(意味着共享),你通常也希望它是纯代码。
5.3 函数库链接的5个特殊秘密
5个基本的、不明显的约定(UNIX链接):
1. 动态库文件的扩展名是“.so”,而静态库文件的扩展名是“.a”
2. 例如,你通过-lthread选项,告诉编译链接到libthread.so。
传给C编译器的命令行参数里并没有提到函数库的完整路径名。它甚至没有提到在函数库目录中该文件的完整名字!实际上,编译器被告知根据选项-lname链接到相应的函数库,函数库的名字是linbname.so——换句话说,“lib”部分和文件的扩展名被省掉了,但在前面加一个“l”。
3. 编译器期望在确定的目录找到库
4. 观察头文件,确定所使用的函数库。
头文件的名字通常并不与它所对应的函数库名相似。函数库链接所存在的另一个不一致就是函数库所包含的某个函数的原型可能与其他头文件中所声明的函数的原型一样。
5. 与提取动态库中的符合相比,静态库中的符合提取的方法限制更严
在静态链接和动态链接的链接语义上还存在一个额外的巨大区别,archive(静态库)与共享对象(动态库)的动作不同。在动态链接中,所有的库符号进入输出文件的虚拟地址空间中,所有的符合对于链接在一起的所有文件都是可见的。相反,对于静态链接,在处理archive时,它只是在archive中查找载入器当时所知道的未定义符号。
在编译器命令行中各个静态链接库出现的顺序是非常重要的。
5.4 警惕Interpositioning
Interpositioning(有些人称它为“interposing”)就是通过编写与库函数名同名的函数来取代该函数库的行为。
使用Interpositioning很容易发生自己代码中某个符号的定义取代函数库中的相同符号的意外。不仅你自己所进行的所有对该函数库的调用将被自己版本的函数调用所取代,而且所有调用该库函数的系统调用也将用你的函数取而代之。
5.5 产生链接器报告文件
5.6 轻松一下——看看谁在说话:挑战Turing测验