链接器做了什么?

December 09, 2023
测试
测试
测试
测试
1 分钟阅读

摘自《程序员自我修养》


机器指令的历史

为了更好的理解计算机程序的编译和链接的过程,我们简单地回顾计算机程序开发的历史一定会非常有益。 最原始的设备是就是纸带,即在纸带上打相应的孔。 这个过程我们可以通过图2-6来看到,假设有一种计算机,它的每条指令是一个字节,也就是8位。它的高4位是0001,表示这是一种跳转指令,低4位存放的是跳转目的地的绝对地址。我们可以从图2-6中看到,这个程序的第一条指令就是一条跳转指令,它的目的地址是第5条指令(注意,第5条指令的绝对地址是4).至于0和1怎么映射到纸带上,这个应该很容易理解,比如我们可以规定纸带上每行有8个孔位,每个孔位代表一位,穿孔表示0,未穿孔表示1。

Markdown
Markdown

链接的原因

在一个程序被分割为多个模块以后,这些模块之间最后如何组合形成一个单一的程序是须要解决的问题。模块之间如何组合的问题可以归结为模块之间如何通信的问题,最常见的属于静态语言的C、C++之间通信的方式,一种是模块之间的函数调用,另外一种是模块间的变量访问。函数访问须知道目标函数的地址,变量访问也须知道目标变量的地址,所以这两种方式都可以归结为一种方式,那就是模块之间的符号引用。模块间依靠符号来通信类似于拼图版,定义符号的模块多出一个区域,引用该符号的模块刚好烧录那一块区域,两者的拼接刚好完美组合。这个模块组合的过程就是链接。

Markdown
Markdown

静态链接

链接过程主要包括了地址和空间分配符号决议重定位等这些步骤。

符号决议有时候也被叫做符号绑定(Symbol Binding)、名称绑定(Name Binding)。甚至还有叫做地址绑定(Address Binding)、指令绑定(Instruction Binding)的,大体上它们的意思都一样,但从细节角度来区分,它们之间还是存在一定区别的,比如“决议”更倾向于静态链接,而“绑定”更倾向于动态链接,即它们所使用的范围不一样。在静态链接中,我们统一称为“符号决议”。

最基本的静态链接过程如图2-8所示。编译过程如下图:

Markdown
Markdown

现代编译和链接过程并非想象那么复杂,它还是一个容易理解的概念,比如我们在程序模块main.c使用另外一个模块func.c中的函数foo()。我们在main.c模块中每一处调用的foo的时候都必须确切知道foo函数的地址,所以它暂时把这些调用foo的指令的目标地址搁置,等待最后链接的时候由链接器去将这些指令的目标地址进行修正,则填入正确的foo函数地址。当func.c模块重新编译,foo函数的地址有可能改变时,那么我们在main.c中所有使用到foo的地址的指令将要全部重新调整。这些繁琐的工作将成为程序员的噩梦。使用链接器,你可以直接引用其他模块的函数和全局变量而无需知道它们的地址,因为链接器,你可以直接引用其他模块的函数和全局变量而无须知道它们的地址,因为链接器在链接的时候会根据引用的符号foo,自动去相应的func.c模块查找foo的地址,然后将main.c模块中所有引用到foo的指令重新修正,让它们的目标地址为真正的foo函数的地址。这就是静态链接的最基本功能和作用。 在链接过程中,对其他定义在目标文件中的函数调用的指令须要被重新调整,对使用其他定义在其他目标文件的变量来说,也存在同样的问题。让我们结合具体CPU指令来理解这个过程,假设我们有个全局变量,比如我们在目标文件中B里面有一个指令: movl $0x2a, var 这条指令就是给这个var变量赋值0x2a,相当于C语言中的语句var=42,然后我们编译目标文件B,得到这条指令机器码,如图2-9所示:

Markdown
Markdown

由于在编译目标文件B的时候,编译器并不知道变量var的目标地址,所以编译器在无法确定地址的情况下,将这条mov指令的目标地址置为0,等待链接器在将目标文件A和B链接起来的时候再将其修正。

继续阅读

更多来自我们博客的帖子

如何安装 BuddyPress
由 测试 December 17, 2023
经过差不多一年的开发,BuddyPress 这个基于 WordPress Mu 的 SNS 插件正式版终于发布了。BuddyPress...
阅读更多
Filter如何工作
由 测试 December 17, 2023
在 web.xml...
阅读更多
如何理解CGAffineTransform
由 测试 December 17, 2023
CGAffineTransform A structure for holding an affine transformation matrix. ...
阅读更多