链接器做了什么?
摘自《程序员自我修养》
机器指令的历史
为了更好的理解计算机程序的编译和链接的过程,我们简单地回顾计算机程序开发的历史一定会非常有益。
最原始的设备是就是纸带,即在纸带上打相应的孔。
这个过程我们可以通过图2-6来看到,假设有一种计算机,它的每条指令是一个字节,也就是8位。它的高4位是0001,表示这是一种跳转指令,低4位存放的是跳转目的地的绝对地址。我们可以从图2-6中看到,这个程序的第一条指令就是一条跳转指令,它的目的地址是第5条指令(注意,第5条指令的绝对地址是4).至于0和1怎么映射到纸带上,这个应该很容易理解,比如我们可以规定纸带上每行有8个孔位,每个孔位代表一位,穿孔表示0,未穿孔表示1。
链接的原因
在一个程序被分割为多个模块以后,这些模块之间最后如何组合形成一个单一的程序是须要解决的问题。模块之间如何组合的问题可以归结为模块之间如何通信的问题,最常见的属于静态语言的C、C++之间通信的方式,一种是模块之间的函数调用,另外一种是模块间的变量访问。函数访问须知道目标函数的地址,变量访问也须知道目标变量的地址,所以这两种方式都可以归结为一种方式,那就是模块之间的符号引用。模块间依靠符号来通信类似于拼图版,定义符号的模块多出一个区域,引用该符号的模块刚好烧录那一块区域,两者的拼接刚好完美组合。这个模块组合的过程就是链接。
静态链接
链接过程主要包括了地址和空间分配,符号决议和重定位等这些步骤。
符号决议有时候也被叫做符号绑定(Symbol Binding)、名称绑定(Name Binding)。甚至还有叫做地址绑定(Address Binding)、指令绑定(Instruction Binding)的,大体上它们的意思都一样,但从细节角度来区分,它们之间还是存在一定区别的,比如“决议”更倾向于静态链接,而“绑定”更倾向于动态链接,即它们所使用的范围不一样。在静态链接中,我们统一称为“符号决议”。
最基本的静态链接过程如图2-8所示。编译过程如下图:
现代编译和链接过程并非想象那么复杂,它还是一个容易理解的概念,比如我们在程序模块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所示:
由于在编译目标文件B的时候,编译器并不知道变量var的目标地址,所以编译器在无法确定地址的情况下,将这条mov指令的目标地址置为0,等待链接器在将目标文件A和B链接起来的时候再将其修正。
上一篇: Swarm系列7--存储介绍
下一篇: C和C++的一些区别(一)
推荐阅读
-
为什么在html设置了禁用浏览器缓存,但点前进后退时页面还是 返回 From cache_html/css_WEB-ITnose
-
编译原理入门——什么是编译器?
-
技术-什么时候需要tomcat服务器,什么时候需要自己搭建服务器
-
请问php实现服务器自动登录网站后台用什么函数
-
node链接mongodb数据库的方法详解【阿里云服务器环境ubuntu】
-
手机站有没有什么好用的文本编辑器推荐
-
editplus可以代码跟踪吗?或者用什么轻巧的编辑器
-
laravel5.2为什么在服务器上获取不到session,storage下的sessions下的文件里面存储的有?
-
这种从微信浏览器中直接跳转到APP用的是什么技术?
-
SQLSERVER服务器链接连接(即sqlserver的跨库连接)_MySQL