编译与链接(一)——编译

一个项目为什么就可以按照其设计思想运行,一个小的程序为什么就可以正确执行,一个printf()函数为什么就能打印出我们想要的东西……这一切究竟是什么样的过程?计算机都是做了什么工作?有写过程序的都会说:编写的程序编译成二进制代码,然后连接成可执行文件,计算机运行的可执行文件啊。流程是这样,但是有没有对这个过程感觉好奇呢?这究竟是一个什么样的过程呢,C程序究竟怎么变成的二进制文件?目标文件究竟怎样变成的可执行文件?计算机又是怎样运行可执行文件的?作为PC机程序开发者或许不用关心这些,但是作为一个嵌入式程序员了解这个过程还是很有用处的,下面我们一一了解。

从源文件到可执行文件,其流程具体如下(图1):我们以C程序为例

本文首先介绍编译过程,一个源程序怎么就变成了二进制的目标文件,这个过程都有哪些环节,各个环节都做了什么工作?其整个过程如上图红色部分。

第一个环节是预编译,把C程序预编译成.i文件,这个文件也是txt格式,与C程序格式上没什么区别。该环节所做的工作: (1)展开所有的宏,去掉”#define”;(2)处理所有的条件预编译指令;(3)处理”#include”预编译指令,将被包含的文件插入到该预编译指令的位置;(4)删除所有的注释;(5)添加行号和文件标识,便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告是能够显示行号,我们C程序定义的宏定义__LINE__,就是因为有这个过程才能够显示行号;(6)保留所有的#pragma编译指令,编译器需要用到。

第二个环节是编译,这个环节是把预处理好的文件编译成汇编代码,这个环节是整个编译过程的最重要也是最困难的环节。该环节包括词法分析、语法分析、语义分析以及目标代码优化。其过程可以表示如下(图2):

词法分析其实起到扫描器的作用,将源代码的字符序列分割成一系列的几号。例如语句array[index]=(index+4)*(2+6),分割成index, [, ], = ……;产生的记号一般分为以下几类:关键字、标识符、字面量(数字,字符串等)和特殊符号(+, =)。

语法分析器将由扫描器产生的记号进行语法分析,从而产生语法树,语法树是以表达式为节点的树。上面的表达式生成语法树用如下图所示(图3):

语义分析器进行静态语义分析分析,所谓静态语义是指在编译期间可以确定的语义,动态语义是在运行期间才能确定的语义。静态语义通常包括声明和类型匹配,类型转换。语义分析就是把语法树的表达式都标识了类型,如下图所示(图4):

接下来的步骤是源代码优化,由源代码优化器完成,例如把(2+6)优化成立即数8。

最后是目标代码生成和优化,代码生成器将中间代码转化成目标机器代码,这个 过程依赖于目标机器。不同的机器有着不同的字长,寄存器,整数数据类型和浮点数据类型等。然后还对代码进行优化,例如乘法通过位移完成。

总的来说,编译过程就是把source code编程依赖于目标机器的汇编语言,由编译器生成的汇编语言与直接编程写出来的汇编语言有所不同,与手工写的汇编语言相比,该汇编语言经过了优化。

编译过程的最后就是汇编,该过程比较简单,只是根据汇编指令和机器指令的对照表一一翻译就可以了。这样就生成了二进制的目标文件。

到这里,我们介绍完了整个的编译过程,有不足或错误的地方请大家指出,我对这个过程的理解还不够,需要各位多多指教。