编译一个c或c++程序的时候,总是使用gcc命令。gcc其实是根据不同的参数去调用预编译器ccl,汇编器as,链接器ld。预编译器ccl将源代码编译成汇编代码,汇编器as将汇编代码转成机器指令,生成目标文件,链接器ld将目标文件连接成可执行文件。
汇编器as已经将汇编代码转成可执行的机器指令了,为什么还要连接呢?当开发项目的时候,可能会有很多人协作,可能有很多功能独立的模块。一般的做法是将独立的模块分开实现,独立编译,最后再将不同的模块组装起来。这时需要一个可以将不同模块代码组装成一个完整的可执行文件的工具,这个工具就是链接器ld,而这个组装的过程就是链接。
在了解连接之前,我们先来看看目标文件的结构,看看目标文件中有什么内容,是如何组织的。目标文件是一种二进制文件,以段的形式组织。在目标文件最开始处是一个固定长度的文件头,文件头记录了该文件的基本信息,包括该文件的入口地址、段表的位置、段表的长度等。通过文件头,可以找到段表,通过段表,可以找到文件中所有的段,从而遍历整个文件。文件中主要的段有代码段和数据段,分表存放文件的执行命令和数据。
文件头 |
段表 |
代码段 |
数据段 |
符号表 |
在目标文件中有一种段叫做符号表,符号表中记录了定义在本目标文件中的全局符号和本目标文件中引用的全局符号。全局符号指的是全局函数或全局变量。在链接过程中,通过符号表,可以找到所有不在本模块的全局符号,通过全局符号将不同的模块组织起来。
静态链接的方式很浪费计算机内存和磁盘空间。几乎每个程序都使用到printf()、scanf()等公用库函数,有多少个进程在运行,就意味着有多少份printf()、scanf()的代码存放在内存中;有多少个程序就有多少份printf()、scanf()的代码存放在硬盘中,这造成系统资源的浪费。静态链接对程序的更新、部署、发布带来了很多的麻烦。一旦程序中有任何模块更新了,整个程序需要重新链接,然后将整个程序发布给用户。
要解决空间浪费和更新困难这两个问题,最简单的方法就是将程序的模块相互分割开,等到程序运行时才进行链接,这就是动态链接的基本思想。
在动态链接中,链接器ld与普通共享对象一样被映射到进程的地址空间,在系统开始运行程序时,系统先将控制权交给链接器ld,等完成所有链接工作之后再将控制权交给进程。