[翻译] 简谈编译、汇编、链接和加载 – part III
W.3 重定位记录(relocation records)
由于不同的对象文件会互相引用对方的代码和/或数据,所以不同的位置,这些不需要在链接时合并。比如,在下面的图中,包含main()的对象文件含有对funct()和printf()函数的调用。把所有对象文件链接在一起后,链接器使用重定位记录来定位那些需要填充的地址。
W.4 符号表
汇编成机器代码的过程会把代码中所有的标签移除,对象文件格式需要把这些信息存放在不同的地方。符号表就是做这个用的。符号表包含了一个名字及其在text和data块的偏移量的列表。反汇编程序可以把这个过程从对象文件或可执行文件翻译回来。
W.5 链接
链接让分开编译成为可能。如下图所示,一个可执行文件可以由多个可独立编译的源文件编译而来,而且每个文件都可以编译为自己的对象文件。
5.1 共享对象
在一个典型的系统上会有多个程序同时运行。每个程序都依赖于很多函数。而这些函数有很多是标准的C语言的库函数,比如printf(), malloc(), strcpy()等,有些则是非标准的或程序员自定义的函数。
如果每个程序都使用标准的C库(library),那就意味着每个程序都要在它的(地址空间)内部有一份C库。这样就会浪费很多资源,也影响效率和性能。
既然对C库文件的需求是共通的,那么如果每个程序都可以共享访问一份C库文件就是一个更好的选择,而不是每个程序都把标准C库文件加载一份到自己的内存地址中。
这个功能也是在链接过程中来实现的。有些对象是在链接的时候链接进来,而有些则是在程序运行时才链接进来(称为延迟链接或动态链接)
5.2 静态链接
静态链接表示一个程序和它需要的库文件在链接阶段就被链接器合并在一起了。这也意味着这个程序和库文件之间的绑定是固定的,且在程序开始运行之前就是已知的。同时也意味着我们无法改变这个结合状态,除非使用一个新版的库文件重新链接这个程序。
静态链接的程序所用的库(对象)一般是一个以.a为扩展名的文件。一个常见的例子就是标准的C库,libc.a。
如果不确定程序运行时能得到正确的库文件版本,或者想要测试一个新版的库文件,还不想把它作为共享库文件安装,这时可以使用静态链接。
对gcc来说,在编译/链接时使用-static选项会得到静态链接的程序
gcc –static filename.c –o filename
静态编译出的可执行文件会大很多,因为所有需要的信息和文件都要被放入可执行文件中。
5.3 动态链接
动态链接表示程序和它使用的特定库文件并非链接阶段就被链接在一起。相反,链接器在可执行文件中放入特定信息,告诉加载器(loader)代码在哪一个共享对象模块中以及应该使用哪一个运行时链接器来找到这个对象文件并将它与程序绑定。这意味着程序和共享对象之间的绑定是在运行时完成,且在程序开始执行之前。
这种类型的程序称为部分绑定可执行文件,因为它并非全部(符号或引用)可解析的。链接器在链接时间并不会将程序中引用的符号和库文件的特定代码结合起来。相反,链接器只是留一些类似这样的信息:这个程序调用的这个共享对象的这个函数,我只是记一下这个函数在哪个共享对象中。现在我要继续下面的工作。
共享对象中的符号会被验证以确认它们确实存在,但不会被合并到程序中。
链接器在可执行程序中存储了外部库文件的地址,用于找到哪些无法在程序中找到的符号。这造成的实际效果就是把(符号与代码之间的)绑定推迟到了程序运行时。
动态链接程序使用的共享对象通常使用.so的扩展名。一个最常见的例子就是标准C库libc.so。
动态链接的好处有:
1. 可执行文件的大小会比静态链接的文件小很多
2. 共享库文件可自行升级而不需要重新链接引用它的程序。
3. 软件商只需提供需要的库文件模块。
4. 结合虚拟内存技术,动态链接允许两个或更多进程共享一个只读的可执行模块,如标准C库。使用这个技术,任意时间只有一份库文件需要保留在内存中。节约了内存的使用。