概述
在这里持续会开一个编译器的专题。
自学资料来自于google and dragon book。
截止日期为:20211231
不同语言对应的不同编译器
溯源
编译器
小demo
假设我们在编辑器中输入了a = b + 1,下图为编译器的整个编译流程。
词法分析
如果把编译器看做一个黑盒子,它能够把源程序映射为在语义上等价的目标程序。
首先我们的源程序会进入词法分析阶段。词法分析读入源程序输入的字符流,并且生成如下所示的词法单元:<token-name, attribute-value>。在这个词法单元中,第一个分量token-name作为语法分析阶段需要使用的抽象符号,第二个分量attribute-name指向符号表中关于这个词法单元的解释信息。其中符号表是制作编译器的时候就已经写好的。
假设我们输入的源程序语句为:a = b + 1
这个赋值语句会在词法分析阶段组合成以下词素:
- a 是一个词素,被映射为词法单元<id, 1>,其中id表示标识符identitifier的抽象符号,1 指向符号表中 a 对应的条目。一个标识符对应的符号表条目存放该标识符对应的相关信息,例如他的名字和类型等。
- 赋值符号 = 是一个词素,被映射为词法单元<=>。因为这个词法单元不需要属性值,所以我们省略了第二个分量。其实也可以使用assign这样的抽象符号表示赋值操作,但是为了标记上的方便我们使用了 = 。
- b 也是一个词素,被映射为词法单元<id, 2>,其中 2 指向 b 在符号表中对应的条目。
- + 是一个词素,被映射为<+>。
- 1 是一个词素,被映射为<1>。
语法分析
编译器的第二个步骤被称为语法分析或解析。
语法分析阶段使用词法分析产生的词法单元构造语法分析树。树中的每个内部节点表示一个运算,而该节点的叶子结点表示该运算的分量。
语义分析
语义分析器使用语法树和符号表中的信息来检查源程序是否和语言定义的语义一致。它同时收集类型信息,并且把这些信息存放在语法书或符号表中,以便在后续过程中使用。
语义分析的一个很重要的阶段是类型检查。编译器检查每个运算符是否具有匹配的运算分量。比如,很多程序设计语言中的定义中要求一个数组的下标必须是整数,如果用一个浮点数作为下标,编译器就必须报告错误。
程序设计语言也允许某些类型转换,这些被称为自动类型转换。比如整数和浮点数相加,整数会自动转化为浮点数进行计算。程序a = b + 1中我们假设 b 是浮点数,所以语义分析阶段会出现 inttofloat 的转换。
中间代码生成
这个过程是把源程序翻译为目标代码的过程。一个编译器可能会构造出一个或多个中间表示。语法树就是一种中间表示,它通常在语法分析和语义分析阶段生成。
在源程序经过语法分析和语义分析之后,很多编译器会生成一个明确的低级的类机器语言的中间表示。我们可以把这个看作某个抽象机器的程序。该中间表示具有两个重要的性质:易于生成并且能够被轻松翻译为目标机器代码。
如上图所示,我们一般会考虑采取三地址代码。
代码优化
经过优化阶段,会生成更好的目标代码。更好意味着:能耗更低。这个阶段是非常重要的。
代码生成
代码生成器以源程序的中间表示作为输入,把它映射为目标机器语言。如果目标机器语言是机器代码,那么就必须为程序使用的每个变量选择寄存器或内存位置。代码生成的一个至关重要的步骤是为程序合理分配寄存器和内存空间。
以上算是对编译原理的一个入门概述,接下来会跟 Fredrik Kjolstad 的课程进入编译原理的世界。
感谢
感谢郑大的某位教授为我打开代码实现原理的一扇窗。
感谢Assistant Professor Fredrik Kjolstad和的公开讲义。
参考文献
-
编译原理第二版
-
Assistant Professor Fredrik Kjolstad 讲义