编译和链接

你是否有过这样的疑问:我们编写的源代码,经过了现代强大IDE的处理,最终生成了可执行文件,那么集成开发环境到底执行了哪些具体的动作,经历了哪些过程生成了可执行文件呢?带着这个疑问,我们一起来看一看。

从源代码到可执行文件,中间经历了这几个阶段:预处理、编译、汇编、链接。

预处理

预处理主要的工作为:

  1. 替换展开源代码中各个使用宏定义#define的地方
  2. 处理源代码中所有的条件预编译指令如#if、#elif、#else等
  3. 展开源代码中所有的#include预编译指令,将所包含的头文件插入到预编译指令的地方(递归进行)
  4. 删除所有的注释符号//及/× ×/
  5. 添加行号及文件名标识,以便在编译出错时进行提示
  6. 保留所有的#pragma编译器指令,因为这个指令需要被编译器用到

以linux为例,通过gcc -E a.c -o a.i即可生成预处理后的文件,通过查看预处理后的文件可以定位源码中包含的宏定义引用、头文件插入是否正确。源文件经过预处理,做完准备工作后就可以进行编译了。

编译

编译阶段是整个目标文件生成过程中相对较复杂的阶段,编译阶段需要进行将源码进行扫描、语法分析、语义分析、源代码优化、目标代码生成、目标代码优化这些步骤,最终生成汇编文件。

首先我们来看一看扫描的过程:扫描主要是将源代码中的标识符、关键字、字面量(包含数字、字符串等)、特殊符号(如加号、乘号、等号等)输入到扫描器,扫描器根据不同用户设定的语法规则进行源码符号的扫描。

完成源码扫描之后,语法分析器就会分析扫描器中的记号,从而形成“语法树”(由语言分析器产生的以表达式为节点的树)。比如:a = (2 + 1) * c这个表达式,将会生成如下语法树:

我们可以看到,整个语法树是一个赋值表达式,标识符变量和数字不能由其他的表达式组成,所以作为叶子节点,左子树只有一个叶子节点,右子树中有加法表达式和乘法表达式。在语法分析阶段,大多数的语法规则优先级及含义就被确定下来了。当语法分析器发现扫描其中的一些非法规则时(比如括号不匹配、表达式中缺少操作符等)就会报编译错误。

语法分析完后就是语义分析,语义分析主要是进行声明和类型的匹配、类型转换。在语法分析阶段,语法分析器只能确定这个表达式有没有语法错误,而不会进行语义判断,比如两个地址变量(指针)相乘有没有意义。语义分析器对语法分析树中的每个节点进行类型分析转换及标识,进行声明和类型匹配的分析,如果类型不匹配,语义分析器会先进行分析,比如一个浮点类型赋值给整型,语义分析器需要完成这个步骤同时进行提示;一个浮点类型赋值给一个指针就会报编译错误;函数声明和实现不一致会报编译错误。可以在编译阶段确定语义的叫静态语义,在运行阶段确定语义的叫动态语义,比如除0就是动态语义错误。

接下来就是源代码优化,现代编译器有许多不同层次的优化,比如某些变量在多次访问时为了访问速度的提升,会暂时放在寄存器中不进行回写;在编译阶段可以确定某些表达式值的就会直接使用值来简化运算等等。上面我们提到的表达式a = (1 + 2) * c就会直接被优化为a = 3 * c;

源代码优化完成后需要生成目标代码和目标代码优化。生成目标代码就是代码生成器将经过上述步骤后的代码翻译成汇编代码,这个过程非常依赖目标机器(不同的机器有不同的指令集、字长、寄存器等)。代码生成器生成汇编代码后,目标代码优化器会将目标代码进行优化,比如选择合适的寻址方式、删除多余指令、使用更简便的方式替换运算方式等。

使用gcc -S a.i -o a.s可以完成上述步骤。

汇编

汇编器的工作其实比较简单,就是将编译通过后的目标代码通过一定的规则翻译成机器代码。但是有个问题,比如在表达式中引用了其他文件中的变量或者调用了其他文件中的函数,在汇编阶段是无法确认这些变量或函数的地址的,所以在编译阶段会将引用其他地方的变量或函数地址填充一个随机值(一般为0),然后在链接过程中更新所有引用这些变量或函数的地方。

使用gcc -c a.s -o a.o来完成汇编。

链接

现代程序语言设计中,基本上程序都是分层分模块设计的,所以在本模块中应用其他模块的变量/函数非常常见。那么链接器怎样做到确定这些符号的地址呢?

最早的链接器是通过人工计算相应的地址偏移,然后进行填充,但这个对于程序员来说简直不能忍受,于是人们使用符号标识来表示对应的变量及代码段,在变量或函数在多个模块间引用时,可以通过符号来标识地址跳转,最后由链接器来更新这些标识符的地址。所以链接器的主要工作就是地址和空间分配、符号决议(符号标识和地址对应)和重定位(符号标识地址修正更新,重定位)。

使用gcc a.o b.o -o a.out(不指定输出文件名则默认为a.out)完成目标文件与库链接生成可执行文件的过程。

好了,编译和链接的简单介绍就到这里了,如果有什么不对的或者大家感兴趣的内容欢迎留言评论哦。