中间代码生成(Intermediate Code Generation)
本文主要是对 哈工大编译原理课件 的学习和总结。
在语法制导翻译过程中,将生成中间代码的代码(抽象语法树的代码)嵌入到语义动作中,即可完成中间代码(抽象语法树)的生成。
经典的中间代码通常包括以下几种:
- 树和有向无环图(DAG):是比较 high level 的表示形式。例如抽象语法树。
- 三地址码(3-address code):是比较 low level 的表示形式,接近目标机器代码。
- 控制流图(CFG):是更精细的三地址码,程序的图状表示,图中的每个节点是一个基本快(BB),基本块内的代码是三地址码。适合做程序分析。
- 静态单赋值形式(SSA):更精细的CFG,同时包含控制流和数据流的信息。可以简化程序分析算法。
本文将分别介绍各种类型的语句的翻译。
申明语句的翻译
介绍申明语句的翻译前,需要先了解下类型表达式。
类型表达式
首先,基本类型是类型表达式。例如:
- integer
- real
- char
- boolean
- type_error(出错类型)
- void(无类型)
再者,将类型构造符 (type constructor) 作用于类型表达式可以构成新的类型表达式。例如:
- 数组构造符array:若T是类型表达式,则array ( I, T )是类型表达式( I是一个整数)。
类型 类型表达式 int[3] array(3, int) int[2][3] array(2, array(3, int)) - 指针构造符pointer:若T 是类型表达式,则 pointer ( T ) 是类型表达式,它表示一个指针类型。
- 笛卡尔乘积构造符×:若T1 和T2 是类型表达式,则笛卡尔乘积 T1 × T2 是类型表达式。
- 函数构造符→:若T1 、T2 、…、Tn 和R是类型表达式,则 T1×T2×…×Tn → R 是类型表达式。
- 记录构造符record:若有标识符N1、N2 、…、Nn 与类型表达式T1、T2、…、Tn ,则 record ( (N1 × T1) × (N2 × T2)× …× ( Nn × Tn )) 是一个类型表达式。
例如,下面的C程序片段:
struct stype {
char[8] name;
int score;
};
stype[50] table;
stype* p;
- 和stype绑定的类型表达式:record((name×array(8, char)) × (score×integer))
- 和table绑定的类型表达式:array (50, stype)
- 和p绑定的类型表达式:pointer (stype)
申明式语句翻译
对于声明语句,语义分析的主要任务就是收集标识符的类型等属性信息,并为每一个名字分配一个相对地址:
- 从类型表达式可以知道该类型在运行时刻所需的存储单元数量称为类型的宽度 (width)
- 在编译时刻,可以使用类型的宽度为每一个名字分配一个相对地址
而名字的类型和相对地址信息保存在相应的符号表记录中。
下面看一个变量申明语句的SDT:

对于上述文法,可以计算出有相同左部产生式的可选集:
产生式 | 可选集(Select) |
---|---|
D → T i d ; D D\rightarrow T\ id;D D→T id;D | { ↑ , i n t , r e a l } \{\uparrow,int, real\} { ↑,int,real} |
D → ϵ D\rightarrow \epsilon D→ϵ | {$} |
T → B C T\rightarrow BC T→BC | { i n t , r e a l } \{int, real\} { int,real} |
T → ↑ T 1 T\rightarrow {\uparrow}\ T_1 T→↑ T1 | { ↑ } \{\uparrow\} { ↑} |
B → i n t B\rightarrow int B→int | { i n t } \{int\} { int} |
B → r e a l B\rightarrow real B→real | { r e a l } \{real\} { real} |
C → ϵ C\rightarrow \epsilon C→ϵ | { i d } \{id\} { id} |
C → [ n u m ] C 1 C\rightarrow [num]C_1 C→[num]C1 | { [ } |
可见,具有相同左部产生式的可选集是正交的,因此该文法是LL(1)文法,可以采用自顶向下的文法进行分析。分析过程如下:


简单赋值语句的翻译
赋值语句翻译的主要任务是生成对表达式求值的三地址码。例如:
x = ( a + b ) * c ;
// 翻译成三地址码
t1 = a + b
t2 = t1 * c
x = t2
下面看一个简单赋值语句的翻译过程:
符号的属性为:
符号 | 综合属性 |
---|---|
S | code |
E | code addr |
这个文法是LR文法,可以采用自底向上的LR语法分析方法。语义动作中函数说明如下:
- lookup(name):查询符号表返回 name 对应的记录
- gen(code):增量地生成三地址指令 code
- newtemp( ):生成一个新的临时变量t,返回 t 的地址









数组引用的翻译
将数组引用翻译成三地址码时要解决的主要问题是确定数组元素的存放地址,也就是数组元素的寻址。
- 一维数组。假设每个数组元素的宽度是 w,则数组元素 a [ i ] a[i] a[i] 的相对地址是: b a s e + i ∗ w base+i*w base+i∗w。其中,base是数组的基地址,i*w 是偏移地址
- 二维数组。假设一行的宽度是 w1,同一行中每个数组元素的宽度是w2,则数组元素 a [ i 1 ] [ i 2 ] a[i1][i2] a[i1][i2] 的相对地址是: b a s e + i 1 ∗ w 1 + i 2 ∗ w 2 base+i_1 *w_1 +i_2 * w_2 base+i1∗w