C编译器剖析_5.2.5 中间代码生成及优化_赋值表达式的翻译
2015-04-18 17:53
435 查看
5.2.5 赋值表达式的翻译
在这一小节中,我们来讨论一下赋值表达式的翻译,例如“a=a-b;”或者“a += b;”等。在图5.2.13的下方我们给出了表达式“a+=b;”在语义检查前后的语法树,右下侧的语法树相当于是表达式a = a’+b,但表达式中的a和a’对应同一个语法树结点。按C的语义,语句“a+=b;”中的a只能被求值一次,而不能求值两次。例如,若a结点对应一个函数调用(*f()),则语句“(*f())
+= b;”中的函数f只被调用一次。在图5.2.13的上方是C语句“a = a +b;”的语法树,在该树中有两个语法树结点a,若把a改为表达式(*f()),则我们需要为语句“(*f()) =(*f()) + b;”产生两次对函数f的调用。
图5.2.13 赋值表达式的语法树
对于赋值语句“a = b;”来说,当a是结构体位域成员时,对a的写操作会较复杂。下面我们还是举一个例子来说明。以下结构体对象dt中的位域b1、b2、b3和b4共占用32位内存(即4字节),其所处偏移为4,在中间代码层次,我们可用符号“dt[4]”来表示该内存单元。
struct Data{
int a; //偏移为0
//以下各成员构成的位模式为“b4_b3_b2_b1”,
//其中,低8位为b1,高4位为b4
intb1:8; //偏移为4,pos为0
intb2:16; //偏移为4,pos为8
intb3:4; //偏移为4,pos为24
intb4:4; //偏移为4,pos为28
int d; //偏移为8
} dt;
struct Data * ptr = &dt;
ptr->d = 30;
dt.b2 = val;
当C程序员要通过“dt.b2 = val;”来对位域b2进行赋值时,我们可按以下步骤来实现:
(1)按以下中间代码来构造位模式“b4_b3_val_b1”
t1: val << 8; // LSH指令,左移8位
t2: t1 & 0x00FFFF00; //BAND指令,得到位模式“0...0_0...0_val_0...0”
t3:dt[4] & 0xFF0000FF; //BAND指令,得到位模式“b4_b3_0…0_b1”
t4: t2 | t3; //BOR指令,得到位模式“b4_b3_val_b1”
(2)把位模式“b4_b3_val_b1”赋值给dt[4],即可实现对b2的赋值,同时保持b1、b3和b4不会发生变化。
dt[4] = t4;
当我们面对“ptr->d = 30;”时,UCC编译器为ptr->d产生的中间代码如下所示,由于t7是个临时变量,如果对t7进行赋值,则ptr->num的值并没有发生变化。我们要对t6所指向的内存单元进行赋值,才能改变ptr->num的值。
t5: ptr
t6: t5 + 8; //成员d的偏移为0
t7: *t6;
此时我们可产生形如“(IMOV, t6,30);”的中间代码,其中IMOV表示把30赋值给t6所指向的内存单元,而不是把30赋值给t6。UCC编译器的GenerateIndirectMove函数用于产生IMOV指令。若t6的值存放在寄存器eax中,则最终生成的汇编代码可以是“movl $30, (%eax)”,对应的中间代码可表示为:
*t6 = 30; //而非 t6 = 30;
有了这些基础之后,我们就可以来分析一下赋值表达式的翻译,如图5.2.14所示。第29至60行的WriteBitFiled函数用于实现对位域成员“dt.b2”的赋值操作,按前文的介绍,对于形如“dt.b2= val;”表达式来说,我们要构造位模式“b4_b3_val_b1”来对符号“dt[4]”进行赋值。图5.2.14第46至50行分别用于产生前文中我们介绍过的“左移LSH”、“按位与BAND”、“按位与BAND”和“按位或BOR”这4条指令,从而得到位模式“b4_b3_val_b1”。当C程序员编写的是形如“ptr->b2=val;”的语句,我们需要在第54行产生一条IMOV指令(形如“*t
= val;”)来实现赋值,这需要进行间接寻址;而当C程序员编写的是形如“dt.b2 = val;”的语句时,我们在第57行产生MOV指令(形如“dt[4] = val;”)即可,不必进行间接寻址。对赋值表达式“dt.b2 = val”来说,虽然按C的语义,整个表达式“dt.b2 = val”不可充当左值来使用(即“(dt.b2= val) = 3;”是非法的),但赋值操作后,dt.b2的值即为整个表达式的值。因此,我们在第59行调用函数ReadBitField来读取dt.b2的值,用来作为整个表达式“dt.b2
= val”的值。当“dt.b2 = val;”中的val为常数时,我们可在编译时进行一些常量折叠的处理,避免生成一些不必要的指令,函数WriteBitFiled中的其他代码主要用于这些情况,结合图中的注释不难理解相关代码,我们就不再啰嗦。
图5.2.14 TranslateAssignmentExpression()
当我们面对的是形如“a+=b;”的赋值表达式时,UCC编译器在图5.2.14第5行调用函数TranslateExpression,递归地对与a对应的左子树进行翻译。由图5.2.13右下方的语法树,为了避免对a对应的子树进行重复翻译,我们在第8行把左子树a的根结点当作一个标识符结点OP_ID,这样即使当a为(*f())时,我们也只会产生一次函数调用f。另一个需要特殊处理的是当a为“dt.b2”这样的位域成员时,若我们面对的是形如“dt.b2
+= b;”这样的赋值表达式,则图5.2.14第5行真正调用的函数是我们在上一节图5.2.11中介绍的函数TranslateMemberAccess。当位域成员dt.b2处于赋值号的左侧时,在TranslateMemberAccess函数中,我们并未调用ReadBitFiled进行读操作,因此在图5.2.14第10行我们还要对此进行判断。对于“dt.b2 += b;”而言,当我们第一次访问到dt.b2的子树时,在图5.2.14第5行的函数调用返回后,dst对应的符号为“dt[4]”。由于dt.b2是位域,我们需要在第11行调用ReadBitFiled函数进行读操作,这将产生以下中间代码,其中符号“t2”就保存了dt.b2的值,我们需要在第9行把符号“t2”保存在dt.b2对应子树的根结点,当我们第二次访问到dt.b2对应的子树时,其根结点已被当成一个标识符结点OP_ID,其符号名为“t2”,这样我们就可在稍后产生形如“t3:
t2+b”的加法指令。
t1 : dt[4] << 8;
t2 : t1 >> 16;
图5.2.14第13行递归地调用TranslateExpression来翻译赋值运算符的右子树,第15行调用WriteBitFiled来实现对位域成员的写操作,第16至22行用于处理形如“ptr->d=30;”的赋值运算,我们需要在第21行产生形如“*t = 30;”的IMOV指令用于间接寻址,而第25行产生形如“a = b;”的MOV指令。
在此基础上,我们再来理解“a++”或“++a”这样的运算就轻松了许多,图5.2.15给出了这两个表达式在语义检查后的语法树。若a在执行自加运算前的值为val,按照C语言的语义,经过a++和++a的运算后,a的值都变为(val+1),但表达式“a++”的值为val,而表达式“++a”的值为(val+1)。需要注意的是,在C语言中,“a++”和“++a”都不可以作为左值来使用,即“(a++)
=3;”和“(++a) = 5;”都是非法的;但在C++语言中,“++a”可充当左值使用,即“(++a) = 5;”是合法的,但“(a++) =3;”是非法的。这也是C语言不是C++语言子集的一个例证。
图5.2.15 a++和++a的语法树
结合图5.2.15,我们可以来理解一下对自加运算进行翻译的函数TranslateIncrement,如图5.2.16所示。按照我们在图5.2.14对“a+=b”的翻译,对于“++a”来说,我们递归地调用TranslateIncrement(expr->kids[0])即可完成翻译,这可通过图5.2.16第5行和第22行来实现。但对于“a++”来说,我们要先计算出图5.2.15结点a的值,该值即为整个表达式“a++”的值,之后再进行“a+=1”的运算来使a的值加1。图5.2.16第6行递归地调用TranslateExpression函数完成了对a对应子树的翻译,第7行把该子树置为标识符结点OP_ID,也是为了避免对a子树的多次求值。对于“a++”或“a--”而言,当结点a为位域成员时,我们需要通过第14行的ReadBitField函数来读取该位域的值;在a结点不是形如“t1:*t2”的临时变量时,我们在第16和17行创建一个临时变量来保存表达式“a++”的值;否则,我们面对的是形如“ptr->d”的非位域成员,此时第13至18行的if条件都不成立,我们在第12行即已保存“t1:
*t2;”中的符号t1。接下来,在第19行真正调用的函数是TranslateAssignmentExpression,用于实现对结点a的加1运算,第20行返回“a++”整个表达式的值。
图5.2.16 TranslateIncrement()
在下一节中,我们来分析一下tranexpr.c中的其他函数,例如一元运算表达式的翻译。
在这一小节中,我们来讨论一下赋值表达式的翻译,例如“a=a-b;”或者“a += b;”等。在图5.2.13的下方我们给出了表达式“a+=b;”在语义检查前后的语法树,右下侧的语法树相当于是表达式a = a’+b,但表达式中的a和a’对应同一个语法树结点。按C的语义,语句“a+=b;”中的a只能被求值一次,而不能求值两次。例如,若a结点对应一个函数调用(*f()),则语句“(*f())
+= b;”中的函数f只被调用一次。在图5.2.13的上方是C语句“a = a +b;”的语法树,在该树中有两个语法树结点a,若把a改为表达式(*f()),则我们需要为语句“(*f()) =(*f()) + b;”产生两次对函数f的调用。
图5.2.13 赋值表达式的语法树
对于赋值语句“a = b;”来说,当a是结构体位域成员时,对a的写操作会较复杂。下面我们还是举一个例子来说明。以下结构体对象dt中的位域b1、b2、b3和b4共占用32位内存(即4字节),其所处偏移为4,在中间代码层次,我们可用符号“dt[4]”来表示该内存单元。
struct Data{
int a; //偏移为0
//以下各成员构成的位模式为“b4_b3_b2_b1”,
//其中,低8位为b1,高4位为b4
intb1:8; //偏移为4,pos为0
intb2:16; //偏移为4,pos为8
intb3:4; //偏移为4,pos为24
intb4:4; //偏移为4,pos为28
int d; //偏移为8
} dt;
struct Data * ptr = &dt;
ptr->d = 30;
dt.b2 = val;
当C程序员要通过“dt.b2 = val;”来对位域b2进行赋值时,我们可按以下步骤来实现:
(1)按以下中间代码来构造位模式“b4_b3_val_b1”
t1: val << 8; // LSH指令,左移8位
t2: t1 & 0x00FFFF00; //BAND指令,得到位模式“0...0_0...0_val_0...0”
t3:dt[4] & 0xFF0000FF; //BAND指令,得到位模式“b4_b3_0…0_b1”
t4: t2 | t3; //BOR指令,得到位模式“b4_b3_val_b1”
(2)把位模式“b4_b3_val_b1”赋值给dt[4],即可实现对b2的赋值,同时保持b1、b3和b4不会发生变化。
dt[4] = t4;
当我们面对“ptr->d = 30;”时,UCC编译器为ptr->d产生的中间代码如下所示,由于t7是个临时变量,如果对t7进行赋值,则ptr->num的值并没有发生变化。我们要对t6所指向的内存单元进行赋值,才能改变ptr->num的值。
t5: ptr
t6: t5 + 8; //成员d的偏移为0
t7: *t6;
此时我们可产生形如“(IMOV, t6,30);”的中间代码,其中IMOV表示把30赋值给t6所指向的内存单元,而不是把30赋值给t6。UCC编译器的GenerateIndirectMove函数用于产生IMOV指令。若t6的值存放在寄存器eax中,则最终生成的汇编代码可以是“movl $30, (%eax)”,对应的中间代码可表示为:
*t6 = 30; //而非 t6 = 30;
有了这些基础之后,我们就可以来分析一下赋值表达式的翻译,如图5.2.14所示。第29至60行的WriteBitFiled函数用于实现对位域成员“dt.b2”的赋值操作,按前文的介绍,对于形如“dt.b2= val;”表达式来说,我们要构造位模式“b4_b3_val_b1”来对符号“dt[4]”进行赋值。图5.2.14第46至50行分别用于产生前文中我们介绍过的“左移LSH”、“按位与BAND”、“按位与BAND”和“按位或BOR”这4条指令,从而得到位模式“b4_b3_val_b1”。当C程序员编写的是形如“ptr->b2=val;”的语句,我们需要在第54行产生一条IMOV指令(形如“*t
= val;”)来实现赋值,这需要进行间接寻址;而当C程序员编写的是形如“dt.b2 = val;”的语句时,我们在第57行产生MOV指令(形如“dt[4] = val;”)即可,不必进行间接寻址。对赋值表达式“dt.b2 = val”来说,虽然按C的语义,整个表达式“dt.b2 = val”不可充当左值来使用(即“(dt.b2= val) = 3;”是非法的),但赋值操作后,dt.b2的值即为整个表达式的值。因此,我们在第59行调用函数ReadBitField来读取dt.b2的值,用来作为整个表达式“dt.b2
= val”的值。当“dt.b2 = val;”中的val为常数时,我们可在编译时进行一些常量折叠的处理,避免生成一些不必要的指令,函数WriteBitFiled中的其他代码主要用于这些情况,结合图中的注释不难理解相关代码,我们就不再啰嗦。
图5.2.14 TranslateAssignmentExpression()
当我们面对的是形如“a+=b;”的赋值表达式时,UCC编译器在图5.2.14第5行调用函数TranslateExpression,递归地对与a对应的左子树进行翻译。由图5.2.13右下方的语法树,为了避免对a对应的子树进行重复翻译,我们在第8行把左子树a的根结点当作一个标识符结点OP_ID,这样即使当a为(*f())时,我们也只会产生一次函数调用f。另一个需要特殊处理的是当a为“dt.b2”这样的位域成员时,若我们面对的是形如“dt.b2
+= b;”这样的赋值表达式,则图5.2.14第5行真正调用的函数是我们在上一节图5.2.11中介绍的函数TranslateMemberAccess。当位域成员dt.b2处于赋值号的左侧时,在TranslateMemberAccess函数中,我们并未调用ReadBitFiled进行读操作,因此在图5.2.14第10行我们还要对此进行判断。对于“dt.b2 += b;”而言,当我们第一次访问到dt.b2的子树时,在图5.2.14第5行的函数调用返回后,dst对应的符号为“dt[4]”。由于dt.b2是位域,我们需要在第11行调用ReadBitFiled函数进行读操作,这将产生以下中间代码,其中符号“t2”就保存了dt.b2的值,我们需要在第9行把符号“t2”保存在dt.b2对应子树的根结点,当我们第二次访问到dt.b2对应的子树时,其根结点已被当成一个标识符结点OP_ID,其符号名为“t2”,这样我们就可在稍后产生形如“t3:
t2+b”的加法指令。
t1 : dt[4] << 8;
t2 : t1 >> 16;
图5.2.14第13行递归地调用TranslateExpression来翻译赋值运算符的右子树,第15行调用WriteBitFiled来实现对位域成员的写操作,第16至22行用于处理形如“ptr->d=30;”的赋值运算,我们需要在第21行产生形如“*t = 30;”的IMOV指令用于间接寻址,而第25行产生形如“a = b;”的MOV指令。
在此基础上,我们再来理解“a++”或“++a”这样的运算就轻松了许多,图5.2.15给出了这两个表达式在语义检查后的语法树。若a在执行自加运算前的值为val,按照C语言的语义,经过a++和++a的运算后,a的值都变为(val+1),但表达式“a++”的值为val,而表达式“++a”的值为(val+1)。需要注意的是,在C语言中,“a++”和“++a”都不可以作为左值来使用,即“(a++)
=3;”和“(++a) = 5;”都是非法的;但在C++语言中,“++a”可充当左值使用,即“(++a) = 5;”是合法的,但“(a++) =3;”是非法的。这也是C语言不是C++语言子集的一个例证。
图5.2.15 a++和++a的语法树
结合图5.2.15,我们可以来理解一下对自加运算进行翻译的函数TranslateIncrement,如图5.2.16所示。按照我们在图5.2.14对“a+=b”的翻译,对于“++a”来说,我们递归地调用TranslateIncrement(expr->kids[0])即可完成翻译,这可通过图5.2.16第5行和第22行来实现。但对于“a++”来说,我们要先计算出图5.2.15结点a的值,该值即为整个表达式“a++”的值,之后再进行“a+=1”的运算来使a的值加1。图5.2.16第6行递归地调用TranslateExpression函数完成了对a对应子树的翻译,第7行把该子树置为标识符结点OP_ID,也是为了避免对a子树的多次求值。对于“a++”或“a--”而言,当结点a为位域成员时,我们需要通过第14行的ReadBitField函数来读取该位域的值;在a结点不是形如“t1:*t2”的临时变量时,我们在第16和17行创建一个临时变量来保存表达式“a++”的值;否则,我们面对的是形如“ptr->d”的非位域成员,此时第13至18行的if条件都不成立,我们在第12行即已保存“t1:
*t2;”中的符号t1。接下来,在第19行真正调用的函数是TranslateAssignmentExpression,用于实现对结点a的加1运算,第20行返回“a++”整个表达式的值。
图5.2.16 TranslateIncrement()
在下一节中,我们来分析一下tranexpr.c中的其他函数,例如一元运算表达式的翻译。
相关文章推荐
- C编译器剖析_5.3.2 中间代码生成及优化_switch语句的翻译
- C编译器剖析_5.2.6 中间代码生成及优化_一元表达式及其他表达式的翻译
- C编译器剖析_5.3.1 中间代码生成及优化_If语句和复合语句的翻译
- C编译器剖析_5.4.1 中间代码生成与优化_删除无用的临时变量和优化跳转目标
- C编译器剖析_5.2.3 中间代码生成及优化_通过“偏移”访问数组元素和结构体成员
- C编译器剖析_5.2.4 中间代码生成及优化_后缀表达式的翻译
- C编译器剖析_5.4.2 中间代码生成及优化_基本块的合并
- C编译器剖析_5.2.2 中间代码生成及优化_再论符号symbol与公共子表达式
- C编译器剖析_5.2.1 中间代码生成及优化_布尔表达式的翻译
- C编译器剖析_5.1 中间代码生成及优化_简介
- C编译器剖析_6.3.1 汇编代码生成_由中间指令产生汇编代码的主要流程
- C编译器剖析_6.1 汇编代码生成_简介
- C编译器剖析_6.3.4 汇编代码生成_为函数调用与返回产生汇编代码
- 中间代码生成中的优化
- C编译器剖析_6.3.6 汇编代码生成_为“取地址”产生汇编指令
- C编译器剖析_6.3.2 汇编代码生成_为算术运算产生汇编代码
- 第五章 语法制导翻译及中间代码生成(1)
- C编译器剖析_6.2 汇编代码生成_寄存器的管理
- C编译器剖析_6.3.3 汇编代码生成_为跳转指令产生汇编代码
- AB1601优化与生成代码大小