每个源代码模块独立编译,然后按照需要将他们组装起来,这个组装模块的过程,就是链接。链接的主要内容,就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接。
链接的过程,主要包括了地址和空间分配,负号决议,重定位等步骤。
符号决议有时候也被叫做符号绑定,名称决议;决议更倾向于静态链接,而绑定更倾向于动态链接。
源文件经过编译器编译成目标文件,目标文件和库文件一起链接形成最终可执行文件。而最常用的库就是运行时库,他是支持程序运行的基本函数的集合。库其实是一组目标文件的包,就是一些最常用的编码已编译目标文件打包存放。
很多时候,我们把目标文件称作为模块。
现代的编译和链接过程也并非想象的那么复杂。比如我们在程序模块main.c中使用两外一个func.c中函数方法foo()。我们在main.c的模块中每一处调用foo的时候都必须确切知道foo这个函数的地址,但是每个模块都是单独编译的,在编译器编译main.c的时候,并不知道foo函数的地址,所以他暂时把这些调用foo的指令的目标地址搁置,等待最后连接的时候有编译器将这些指令的目标地址修订。使用连接器,你可以直接饮用其他模块的函数和全局变量而无需知道他们的地址,因为连接器在连接的时候,会根据你所饮用的foo,自动去响应的func.c模块查找foo的地址,然后将main.c模块中所引用的符号foo,自动去响应的func.c模块查找foo的地址,然后将main。C模块汇总所引用的foo的指令重新修正,让他们去响应的func.c模块查找foo的地址,然后将main.c模块中缩影的foo指令重新修正,让他们的目标地址为真正的foo函数的地址。这就是静态链接的最基本过程和作用。
在链接的过程中,对其他定义在目标文件中的函数调用的指令需要被诚信调整,对使用其他定义在其他目标文件的变量来说,也存在同样的问题,让我们结合具体的cpu指令来了解这个过程。假设我们有个全局变量var,他在目标文件A里面。我们在目标文件B里面要访问这个全局变量,比如我们在目标文件B里面有这么一条指令:
Movl s0x2a , var
这条指令就是给这个var变量赋值,相当于c语言中的语句var = 42;然后我们编译目标文件B,得到这条机器码,如图:
Mov指令码 源常量
士大夫
由于编译目标文件B的时候,编译器并不知道变量var的目标地址,所以编译器在没有确定地址的情况下,将这条指令的目标地址置为0,等待连接器在将目标文件A和B连接起来的时候在将其修正。我们假设A和B连接后,变量var的地址确定下来为0x1000,那么连接器将会把这个指令的目标地址部分修正为0x1000,这个地址修订的过程,叫做重定位。每个要修正的地方叫做重定位入口。