1 预编译
- 以C语言为例,C语言的.c文件中包括include,define等等,这些依靠C语言的预处理器(cpp)来进行处理的。
- 将代码包含的#define删除,并在代码中用到宏定义的地方扩展开来;
- 条件编译,如#if,#ifdef,#elif,#endif等语法进行处理;
- 删除程序的注释;
- 将头文件#include加进来,插入到该指令所在位置。
2 编译
预编译后的文件,不再包含注释,头文件也插入进来,条件编译也得到相应的处理。那么,剩下的就是实实在在的源码,需要经过词法分析,语法分析,语义分析及优化,然后产生汇编代码。下面结合具体的代码实例讲解这些过程:
array[index] = (index + 4) * (2 + 6)
2.1 词法分析
- array ---> 标识符
- [ ---> 左方括号
- index ---> 标识符
- ] ---> 右方括号
- = ---> 赋值
- ( ---> 左圆括号
- index ---> 标识符
- + ---> 加号
- 4 ---> 数字
- ) ---> 右圆括号
- * ---> 乘号
- ( ---> 左圆括号
- 2 ---> 数字
- + ---> 加号
- 6 ---> 数字
- ) ---> 右圆括号
这些记号必须符合一定规则,例如一般程序语言都有算术运算,那么符号+,-,*,/等就是合法的记号,同时可能还有运算符重载,既一个符号同时承载了两种及以上的意义。C语言规定标识符必须是数字,字母或者下划线组成,且首字母不能为数字。
2.2 语法分析
语法分析器在上步的基础上,通过Tokens的类型,判断是否符合语法规则。语法分析多采用上下文无关语法,最终生成语法树。上下文无关语法规则描述了程序语言的语法。一般包括一个终止符的有限集,一个非终止符的有限集,一个产生规则的有限集和一个非终止符的开始集。通常程序语言的语法规则通过巴克斯-诺尔范式(BNF)表示。凡是能够通过巴克斯-诺尔范式推到出的都是符合语法规则的表达式。通过语法分析后,程序的形式变为语法树,通过树的数据结构更能表达出程序的本质,也便于进一步处理,而不再是便于人类阅读的形式了。
2.3 语义分析
2.4 中间语言生成
编译器为了对源码程序进行优化,会将上一步生成的语法树转化成中间代码表示,有三地址码和P代码两种方式。在一些功能强大的编译器中,它能对不同语言进行编译,目标代码可以运行在多种平台上,就是利用了中间语言来表示,从而简化编译器设计。上面的代码用三地址码表示:
- t1 = 2 + 6
- t2 = index + 4
- t3 = t2 * t1
- array[index] = t3
经过优化后可表示:
- t2 = index + 4
- t2 = t2 * 8
- array[index] = t2
只用到一个寄存器器和一个内存就能完成上述表达式的运算。将程序转化成中间语言表示,后续的处理过程属于编译器后端。
2.5 目标代码生成和优化
代码生成器将中间语言表示的程序,用对应的汇编表示出来,这里有个重要的主题就是寄存器分配,如何高效的利用寄存器来完成代码转化。每个CPU生产厂家的汇编代码不同,因此需要针对特点的汇编生成不同的目标代码。根据源码优化后的中间代码生成汇编代码:
- movl index, %ecx ; value of index to ecx
- addl $4, %ecx ; ecx = ecx + 4
- mull $8, %ecx ; ecx = ecx * 8
- movl index, $eax ; value of index to eax
- movl %ecx, array(, eax, 4) ; array[index] = ecx
目标代码优化器是在目标代码的基础上进行代码优化,比如选择合适的寻址方式,删除多余的指令等等。