编译器的工作原理

1、编译器是什么?

我们通常用各种程序设计语言去描述计算机的一段计算过程,计算机上的所有运行的软件都是用某种程序设计语言编写,但是这些语言只有被转换成机器码的形式才能 最终被计算机识别并执行。完成这项翻译工作的软件系统被称为编译器(compiler)。

简单地说,一个编译器就是一个程序,它可以阅读某一种(源语言)编写地程序,并将该程序翻译成一个等价地、用另一种语言(目标语言)编写的程序,翻译过程最重要的任务之一就是发现源程序中的错误。

解释器(interpreter)是另外一种语言处理器,它并不通过翻译的形式生成目标程序,从用户的角度看,解释器逐行解释用户的输入源程序,并输出源程序中指定的操作指令。由编译器生成的机器语言目标程序是可以直接被计算机识别的,执行起来通常比解释器快很多。

编译器将源程序映射为在语义上等价目标程序的过程由两部分组成:分析部分和综合部分。

  1. 分析部分(analysis)将源程序分解成多个组成要素,并在这些组成要素之上加上语法结构,然后用这些结构创建一个该源程序的中间表示形式。此外,还会手机有关源程序的信息,并把信息放在一个称为符号表(symbol table)的数据结构中,符号和中间形式一起传送给综合部分。
  2. 综合(synthesis)部分根据中间表示和符号表中的信息来构造用户期待的目标程序。

分析部分通常被称为编译器的前端,综合部分则称为后端,编译器的工作流程由前端和后端的多个流程组成,如下图所示(待补充)。部分编译器在前端和后端之间还有一个机器无关的优化步骤,优化步骤非必须,主要是为了生成质量更高的目标代码程序.

2、编译器的工作流程

2.1 词法分析

词法分析(lexical analysis)是编译器执行的第一个步骤,该步骤读入源程序中的字符流,并将这些字符流组织成有意义的词素(lexeme)的序列。对于灭个词素,词法分析会产生词法单元(token)作为输出:(token-name, attribute-value),词法单元将在下一个语法分析的步骤中使用。假如一个源程序中包含如下的赋值语句position=initial+rate*60在这个赋值语句中的字符可以组成如下词素,并映射成为如下词法单元,这些词法单元会被传递给语法分析阶段。

  1. position是一个词素,被映射成词法单元<id,1>,其中id是表示标识符的抽象符号,而1指向符号表中position对应的条目。一个标识符对应的符号表条目存放该标识符有关的信息,比如它的名字和类型。
  2. 赋值符号=是一个词素,被映射成词法单元<=>。因为这个词法单元不需要属性值,所以我们可以忽略它的第二个分量。也可以使用assign这样的抽象符号作为此法单元的名字,但是为了标记上的方便,我们选择使用词素本身作为抽象符号的名字。
  3. initial是一个词素,被映射成词法单元<id,2>,其中2指向initial对应的符号表条目。
  4. +是一个词素,被映射成词法单元<+>
  5. rate是一个词素,被映射成词法单元<id,3>,其中3指向rate对应的符号表条目。
  6. 是一个词素,被映射成词法单元<>
  7. 60是一个词素,被映射成词法单元<60>经过词法分析之后,上述表达式形成了如下的词法单元序列<id,1><=><id,2><+><id,3><*><60>

2.2 语法分析

语法分析(syntax analysis)是编译器的第二个动作,语法分析器用上一步骤词法分析生成 的词法单元的第一个分量来创建树形的中间表示,该中间表示通常称为语法树(syntax tree),树中的每一个节点表示一个运算,节点的子节点表示该运算的分量。编译器的后续步骤将使用这个语法结构来帮助分析源程序,并最终生成目标程序

2.3语义分析

语义分析主要是使用语法书和符号表中的信息检查源程序是否和语言定义的语法一致,同时也收集类型信息,并将这些信息存放在语法书或符号表中,并在随后的中间代码生成过程中使用。语义分析一个重要部分就是类型检查(type checking),编译器会在该过程中检查每个运算符是否具有匹配的运算分量。例如数组的下标必须是整数,如果用浮点数作为数组下标,编译器就必须报告错误。

2.4中间代码生成

在翻译成目标代码的过程中,一个编译器生成一个或多个中间表示 ,这些中间表示有多种形式,语法树是众多中间表示的一种,这些中间表示通常在语法分析和语义分析中使用。在源程序的语法分析和语义分析完成之后,很多编译器会生成一个明确的低级的或者类机器语言的中间表示。这个中间表示可以看作是抽象机器的程序,需要包含两个性质:

  • 易于生成;
  • 能轻松地翻译为目标机器语言

2.5代码优化

代码优化与机器无关,该步骤主要是改进中间代码以便生成更好的目标代码。更好可能是指 代码运行速度更快、代码长度更短或者是能耗更低

2.6代码生成

代码生成器以源程序的中间表示形式作为输入,并将它映射到目标语言。当目标语言是直接可以运行的机器代码时,那么必须为程序所使用的每个变量选择寄存器或者内存位置。然后,中间指令 被翻译成为能够完成相同任务的机器指令序列 。这个过程最重要的一个方面是合理分配寄存器用以存放机器码执行过程中变量值
上述示例中最终被翻译成如下的机器码:

1
2
3
4
5
LDF   R2, id3
MULF R2, R2, #60.0
LDF R1, id2
ADDF R1, R1, R2
STF id1, R1

2.7 符号表管理

编译器的重要功能之一是记录源程序中使用的变量名,并收集每个变量有关的属性信息。这些属性可能是变量的存储分配、变量类型、变量的作用域等信息。对于中间过程的变量,这些信息还包括参数数量和类型、参数传递方法以及返回类型等。符号表数据结构为每个变量名字创建了一个记录条目,记录的字段就是变量的各个属性。这种数据结构使得编译器可以迅速查找到每个变量的记录,并快速地获取记录值以及更新记录值。