编译过程

注:此处的编译过程是对狭义的编译的内部处理过程进行介绍

编译的4个阶段

狭义的编译大致分为4个阶段

  1. 语法分析
  2. 语义分析
  3. 生成中间代码
  4. 代码生成

语法分析

一般我们所说的编写程序,就是把代码写成人类可读的文本文件的形式。像C和Java这样,以文本形式编写的代码对人类来说的确易于阅读,但并不是易于计算机理解的形式。因为,为了运行C和Java的程序,计算机首先要对代码进行解析,将其转换为计算机易于理解的形式。这里的解析(parse)也称为语法分析(syntax analyzing)。解析代码的程序模块称为解析器(parser)或语法分析器(syntax analyzer)

那么“易于计算机理解的形式”究竟是怎样的形式呢?那就是称为语法树(syntac tree)的形式。顾名思义,语法树是树状结构 ,将代码转化为语法形式的过程如图所示:

编译过程
语法树

语义分析

通过解析代码获得语法树后,接着就是要解析语法树,除去多余的内容,添加必要的信息,生成抽象语法树(AST, Abstract Syntax Tree)这样的一种数据结构。上述的处理就是语义分析(semantic analysis)

语法分析只是对代码的表象进行分析,语义分析则是对表象之外的部分进行分析。

例如,语义分析包括以下处理:

  • 区分变量为局部变量还是全局变量
  • 解析变量的声明和引用
  • 变量和表达式的类型检查
  • 检查在引用变量之前是否进行了初始化
  • 检查函数是否按照定义返回了结果

上述处理的结果都会反映到抽象语法树中。语法分析生成的语法树只是将代码的结构照搬了过来,而语义分析生成的抽象语法树中还包括语义信息。

例如,在变量的引用和定义之间添加链接,适当地添加类型转换等命令,使表达式的类型一致。另外,语法树中的表达式外侧的括号、行末的分号等,在抽象语法树中都被省略了。

生成中间代码

生成抽象语法树后,接着将抽象语法树转化为只在编译器内部使用的中间代码(IR, Intermediate Representation)。之所以特地转化为中间代码,主要是为了支持多种编译语言或机器语言。

例如,GCC不仅支持C语言,还可以用来编译C++和Fortran。CPU方面,不仅是Intel的CPU,还可生成面向Alpha、SPARC、MIPS等各类CPU的机器语言。如果要为这些语言和CPU的各种组合单独制作编译器,将耗费大量时间和精力。Intel CPU用C编译器、C++编译器、Fortran编译器...要制作的编译器的数量将非常庞大。

编译过程
不适用之间代码的情况

如果将所有的编译语言先转化为共同的中间代码,那么对应一种语言或一种CPU,只要添加一份处理就够了。因此支持多种语言或CPU的编译器使用中间代码是比较合适的。

编译过程
使用中间代码的情况

例如,GCC使用的是一种名为RTL(Rigister Transfer Language)的中间代码

根据编译器的不同,也存在不经过中间代码,直接从抽象语法树生成机器语言的情况。

解析代码转化为中间代码为止的这部分内容,称为编译器的前端(front-end)

代码生成

最后把中间代码转换为汇编语言,这个阶段称为代码生成(code generation)。负责代码生成的程序模块称为代码生成器(code generator)

代码生成的关键在于如何来填补编程语言和汇编语言之间的差异。一般而言,比起编程语言,汇编语言在使用上面的限制要多一些。

例如,C和Java可随心所欲地定义局部变量,而汇编语言中能够分配给局部变量的寄存器只有不到30个而已。处理流程控制方面也只是和goto语句功能类似的跳转指令。在这样的限制下,还必须以不改变程序的原有语义为前提进行转换。

优化

除了上诉4个阶段之外,现实的编译器还包括优化(optimization)阶段。

现在的计算机,即便是同样的代码,根据编译器优化性能的不同,运行速度也会数倍的差距。由于编译器要处理相当多的程序,因此在制作编译器时,最重要的一点就是要尽可能地提高编译出来的程序的性能。

优化可以在编译器的各个环节进行。可以对抽象语法树进行优化,可以对中间代码的代码进行优化,也可以对转换后的机器语言进行优化。进一步来说,不仅是编译器,对链接以及运行时调用的程序库的代码也都可以进行优化。

总结

经过上述4个阶段,以文本形式编写的代码就被转换为编译语言。之后就是汇编器和链接器的工作了。