C编译器剖析_5.3.1 中间代码生成及优化_If语句和复合语句的翻译

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产生的代码还是相当高效的。

时间: 2024-10-12 03:37:16

C编译器剖析_5.3.1 中间代码生成及优化_If语句和复合语句的翻译的相关文章

C编译器剖析_5.3.2 中间代码生成及优化_switch语句的翻译

5.3.2.Switch语句的翻译 在这一小节中,我们来讨论一下switch语句的翻译,switch语句的产生式如下所示. SwitchStatement: switch( expr ) statement 当C程序员编写出如下代码时,UCC编译器会在语义检查阶段进行报错"error:The  break shall  appear  in  a  switch  or loop",从语法上来看,以下"case  3: b = 30;"是case语句,而"

C编译器剖析_5.4.2 中间代码生成及优化_基本块的合并

5.4.2  基本块的合并 我们在第5.4.1节时给出了由基本块构成的双向链表和控制流图,为阅读方便,我们这里再次给出"图5.1.4 基本块的静态结构和动态结构".在这一小节中,我们试图把双向链表中相邻的基本块进行合并,当然这种合并需要满足一定条件,同时要保持程序的原有语义.在合并后,控制流图中的前驱与后继关系也要进行调整.我们需要改动的数据结构有图5.4.1中的双向链表和控制流图.需要注意的是,虽然基本块BB5和BB6在双向链表中相邻,但控制流却不会由BB5流入BB6,双向链表只是维

C编译器剖析_5.2.2 中间代码生成及优化_再论符号symbol与公共子表达式

5.2.2    再论符号symbol与公共子表达式 在介绍算术表达式的翻译前,让我们简单重温一下第2.5节中的"图2.5.4 公共子表达式"及"图2.5.5 valueDef和valueUse".为阅读方便,我们再次给出这两张图,更详细的说明请参见第2.5节.对于图2.5.4第2行的a+b,我们会由第7行的中间代码来对a+b进行求值,其结果存于临时变量t1中,之后在第3行中再次遇到表达式a+b时,a和b的值并没有发生变化,我们可在第9行直接把t1赋给变量d.由于我

C编译器剖析_5.2.4 中间代码生成及优化_后缀表达式的翻译

5.2.4 后缀表达式的翻译 在前面的章节中,我们介绍了用于对数组元素和结构体成员进行访问的函数Offset,其接口如下所示,参数addr代表了基地址,参数voff代表可变偏移,而参数coff则代表常量偏移. Symbol Offset(Type ty, Symbol addr,Symbol voff, int coff): 函数Offset的基本想法是产生以下中间代码,我们要先对addr.voff和coff进行相加,得到目标地址(addr+voff+coff),然后再进行"提领Derefere

C编译器剖析_5.1 中间代码生成及优化_简介

5.1  中间代码生成与优化_简介 在语法分析和语义检查阶段,我们始终在与语句Statement.表达式Expression和外部声明ExternalDeclaration这3个概念打交道.通过声明,我们最终建立起了相应的类型结构,并在符号表中保存了相关标识符的类型信息,到了中间代码生成阶段,我们就不再需要处理外部声明ExternalDeclaration了,只需要为语句和表达式生成相应的中间代码即可.文件ucl\tranexpr.c用于为表达式生成中间代码,文件名tranexpr是Transl

C编译器剖析_5.1 中间代码生成及优化_布尔表达式的翻译

5.2  中间代码生成与优化_布尔表达式的翻译 我们仍然按照语法分析和语义检查时的思路,先讨论表达式的翻译,再处理语句.表达式从概念上来说,可分为算术表达式和布尔表达式,在一些编程语言(例如Java)中对这两者是有严格区分的,算术表达式的结果是整数或浮点数,而布尔表达式的结果是逻辑上的真或假.布尔是英国数学家,由于布尔较早进行了关于"与或非"逻辑运算的研究,为了纪念这位先驱,在Java中引入了关键字boolean,而在C++中引入了bool关键字来表达逻辑上的真或假.C语言中并没有专门

C编译器剖析_6.3.6 汇编代码生成_为“取地址”产生汇编指令

6.3.6  为"取地址"产生汇编指令 在这一小节中,我们来讨论一下以下两条中间指令的翻译: (1)取地址指令<ADDR,DST,SRC1,NULL> 例如 <ADDR,t0,  number, NULL>,表示取number的地址并保存到临时变量t0中 (2)对象清零指令< CLR,DST,SRC1,NULL> 例如<CLR,arr,16,NULL>,表示把arr所占16字节的内存清零 我们先举一个例子来说明,对于图6.3.14第4行局

C编译器剖析_6.3.1 汇编代码生成_由中间指令产生汇编代码的主要流程

6.3.1  由中间指令产生汇编代码的主要流程 在这一小节,我们可把关注的焦点放在"如何把某条中间代码翻译成汇编代码"上.UCC编译器的中间代码是如下所示的四元式,包括运算符和3个操作数. <运算符opcode,目的操作数DST,源操作数SRC1,源操作数SRC2> 当然有些中间代码只需要用到opcode和DST就可以了,例如,无条件跳转指令"goto  BB2;"就不需要SRC1和SRC2.为了便于汇编代码的生成,UCC编译器在ucl\X86Linux

C编译器剖析_6.3.5 汇编代码生成_为类型转换产生汇编代码

6.3.5  为类型转换产生汇编代码 在这一小节中,我们来讨论一下整型和浮点型之间的类型转换.有些类型转换并不需要在汇编层次进行数据转换,例如int和unsigned  int之间的转换只是改变了表达式的类型,对数据本身并无影响,以下表达式"(unsigned int) a"对应的二进制数据为0xFFFFFFFF,而表达式"a"对应的二进制数据也为0xFFFFFFFF.但对相同内容的二进制数据来说,进行"有符号整数的右移"和"无符号整数