5.3.1 If语句和复合语句的翻译
我们先简单回顾一下对布尔表达式的翻译,我们通过调用TranslateBranch函数来产生跳转指令,从而实现布尔表达式的语义。在使用函数TranslateBranch(expr, bt, bn)时,有这么两个约定:
(1) 当expr为真时,跳往bt基本块;
(2) 紧随“函数TranslateBranch所生成的跳转指令”之后的基本块为bn。
在表达式的基础上,我们来讨论一下语句的翻译。图5.3.1用于if语句的翻译,第4至8行说明了如何翻译“if(expr) stmt”,我们需要创建trueBB和nextBB这两个基本块;而第12至18行阐述了如何翻译“if(expr) stmt else stmt”语句,我们需要创建trueBB、falseBB和nextBB这3个基本块。图5.3.1第29至34行的代码用于翻译“if(expr)
stmt”,而第35至43行的代码用于翻译“if(expr) stmt else stmt”。
图5.3.1 TranslateIfStatement()
由图5.3.1可以发现,在理解了表达式翻译的前提下,if语句的翻译就较容易理解了。而其他的控制流语句,包括do语句、while语句和for语句也与此类似,我们就不再啰嗦。接下来,我们来讨论复合语句的翻译。
语法上,复合语句的产生式如下所示,由可选的声明列表和语句列表构成。其中的声明可以带有初值。
CompoundStatement --> { DeclarationListopt StatementListopt}
由于局部变量的存储空间是运行时在栈中动态分配的,UCC编译器需要在编译时产生中间代码来实现对局部变量的初始化,而在对应的汇编代码中,由于无法预知相应的存储单元,我们只能采用“ebp寄存器+常量偏移”的模式来对其寻址。程序运行时,我们会在栈中为被调用的函数分配一块内存,用于存放其形参、局部变量、函数返回地址等信息,这块内存通常被称为“活动记录activation
record”或者“帧frame”。大部分的C编译器,会使用x86的ebp寄存器来指向当前函数的活动记录;而“常量偏移”则可由C编译器在编译时,根据局部变量声明在函数中出现的先后顺序和所占内存大小来确定。
对于以下局部数组arr的初始化,我们可以先把arr所占的栈空间清0,然后再根据“初值及其偏移”来产生初始化相应数组元素的指令。
int arr[8] = {10,20,30};
我们在语义检查时介绍过以下结构体struct initData,其中的offset即可用于描述偏移,而expr对应的表达式为初值,而next用于构造链表。
struct initData{
int offset; //偏移AstExpression expr; //初值对应的表达式
InitData next;
};
typedef struct initData * InitData;
上述初值{10,20,30}经语义检查后,我们可得到以下链表,在中间代码生成阶段,我们
可由该链表产生“对相应数组元素进行初始化”的中间代码。
(表达式10,偏移0) --> (表达式20,偏移4) --> (表达式30,偏移8)///////对应的中间代码////////////
arr : 40; //把arr所占40字节栈内存清0
arr[0] = 10;
arr[4] = 20;
arr[8] = 30;
而到了汇编层次,局部变量的名字不再可用,我们改用“ebp寄存器+常量偏移”的方
式来表示局部变量。对应的汇编代码如下所示:
pushl$40
pushl$0
leal-40(%ebp), %eax
pushl%eax
callmemset //对数组arr清0,相当于调用memset(&arr[0], 0, 40);
addl$12, %esp
movl$10, -40(%ebp) //arr[0] = 10;
movl$20, -36(%ebp)
movl$30, -32(%ebp)
对于以下局部数组num来说,由于初值{1,2,3,4}覆盖了数组num所占的所有内存空间,我们并不需要额外产生对数组num进行清0的指令。
int num[4] = {1,2,3,4};
有了这些基础后,我们就来看一下复合语句的翻译,如图5.3.2所示,第1至9行的函
数HasPaddingSpace用来判断初值之间是否有空隙,例如上述arr数组的初值间就有空隙,而数组num之间则没有。这可通过累加各初值所占内存大小,然后再与整个局部对象所占内存大小作比较,即可得到。
图5.3.2 TranslateCompoundStatement()
图5.3.2第17至43行的for循环用来处理各个局部变量,第26行调用GenerateClear
函数,产生“对整个局部对象清0”的指令。第30行判断初值表达式的类型,当表达式为字符串时,我们在第32行调用AddString得到一个代表该字符串的符号;对于其他表达式,则在第36行递归地调用TranslateExpression来翻译。第38行调用CreateOffset函数生成一个形如前文“arr[4]”的符号,第40行通过调用GenerateMove函数产生形如“arr[4]
=20;”的中间代码。图5.3.2第44至47行的while循环递归地调用TranslateStatement来实现对语句的翻译。
在下一小节中,我们来讨论一下switch语句的翻译,transtmt.c中的不少代码与此相关,
由于使用了跳转表的技术,UCC编译器为switch产生的代码还是相当高效的。