编译和链接的过程

来源:互联网 发布:url短网址生成 java 编辑:程序博客网 时间:2024/06/05 10:47

在Linux下,我们用以下GCC生成一个可执行程序的时候

gcc hello.c

实际上会有以下四个阶段:预处理,编译,汇编,链接。


预处理

预处理过程主要处理那些源代码文件只能够的以#开始的预编译指令。比如#include, #define等,主要处理规则如下:

  • 展开所有的宏定义
  • 处理所有条件预编译指令
  • 删除所有的注释
  • 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置
  • 添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号
  • 保留所有的#pragma编译器指令,因为编译器需要使用它们

编译

编译的过程是整个程序的构建的核心部分,也是最复杂的部分之一。主要包括几个阶段:

  • 词法分析
  • 语法分析
  • 语义分析
  • 源代码优化
  • 目标代码生成
  • 目标代码优化

汇编

汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的编译过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译。


链接

我们一般会把每个源代码模块独立地编译,然后按照要求将它们“组装”起来,这个组装模块的过程就是链接。链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。但从原理上讲,它的工作无非就是把一些指令对其他符号地址的引用加以修正。链接过程主要包括了地址和空间分配、符号决议和重定位等这些步骤。

最基本的静态链接过程:每个模块的源代码文件(如.c)文件经过编译器编译成目标文件(如.o),目标文件和库一起链接形成最终可执行文件。而最常见的库就是运行时库,它是支持程序运行的基本函数集合。库其实就是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。

在链接过程中,对定义在目标文件中的函数调用的指令需要被重新调整,对使用那些定义在其他目标文件的变量来说,也存在同样问题。

由于在编译目标文件的时候,编译器并不知道外部引用变量的目标地址,所以编译器在没法确定地址的情况下,将这条mov指令的目标地址设为0,等待链接器在将全部目标文件链接起来的时候再将其修正。假设源文件A和B链接后,A引用B的外部变量var的地址确定下来为0x1000,那么链接器将会把这个指令的目标地址部分修改成0x10000。这个地址修正的过程也叫做重定位,每个要被修正的地方叫一个重定位入口。

PS:m 每个目标文件除了拥有自己的数据和二进制代码外,还提供了三个表:未解决符号表导出符号表地址重定向表


  • 未解决符号表提供了所有在该编译单元里引用但是定义并不是在本编译单元的符号以及其出现的地址;
  • 导出符号表提供了本编译单元具有定义,并且愿意提供给其他单元使用的符号及其地址;
  • 地址重定向表提供了本编译单元所有对自身地址的引用的记录;

编译器将extern声明的变量置入未解决符号表,而不置入导出符号表—外部链接
编译器将static声明的全局变量不置入未解决符号表,也不置入导出符号表,因此其他单元无法使用—-内部链接
普通变量及其函数被置入导出符号表

0 0
原创粉丝点击