编译和链接
编译和链接
编译链接可以分解为4个步骤:预处理、编译、汇编、链接。
预处理
预处理主要处理源代码中以 # 开始的预编译指令。主要处理规则如下:
- 将所有的#define删除,并且展开所有的宏定义。
- 处理所有条件编译指令,如 #if、#ifdef、 #elif、#else、#endif。
- 处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置。这个过程是递归进行的,被包含的文件中可能还包含其他文件。
- 删除所有的注释 // 和 /* */。
- 添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
- 保留所有的 #pragma 编译器指令,因为编译器需要使用它们。
编译
编译过程就是对预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后产生相应的汇编代码文件。
汇编
将汇编代码转换变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。
链接
链接的主要内容就是把各个模块之间的相互引用的部分都处理好,使得各个模块之间能够正确的衔接。
主要包括:地址和空间分配、符号决议、重定位等。
编译器做的工作
词法分析
将源代码输入到扫描器,扫描器简单的进行语法分析,运用一种类似于有限状态机的算法将源代码的字符序列分割成一系列的记号。
词法分析产生的记号一般分为如下几类:关键字、标识符、字面量和特殊符号。
语法分析
语法分析器将对由扫描器产生的记号进行语法分析产生语法树。生成的语法树就是以表达式为节点的树。
语义分析
语法分析仅仅是完成了对表达式的语法层面的分析,但是它并不了解这个语句是否真正有意义。比如C语言里的两个指针做乘法运算是没有意义的,但是这个语句在语法上是合法的。
语义分析器能够分析的语义是静态语义,即在编译器可以确定的语义。
静态语义通常包括声明和类型的匹配,类型的转换。比如当一个浮点数赋值给一个整型表达式时,其中隐含了浮点型到整型的转换,语义分析过程需要完成这个步骤。
动态语义一般是指在运行期出现的语义相关的问题,比如将 0 作为除数是一个运行期语义错误。
中间语言生成
直接在语法树上做优化比较困难,所以源代码优化器往往将整个语法树转换成中间代码。
编译器前端负责产生机器无关的中间代码,编译器后端负责将中间代码转换成目标机器代码。
目标代码生成与优化
编译器后端主要包括代码生成器和目标代码优化器。
代码生成器将中间代码转换成目标机器代码,这个过程依赖于机器,因为不同的机器有着不同的字长、寄存器、整数数据类型和浮点数类型等。
目标代码优化器对上述之代码进行优化,比如选择合适的寻址方式、使用位移来代替乘法运算、删除多余的指令等。