设为首页 加入收藏

TOP

C编译器剖析_5.2.3 中间代码生成及优化_通过“偏移”访问数组元素和结构体成员(一)
2015-11-19 23:06:57 来源: 作者: 【 】 浏览:19
Tags:编译器 剖析 _5.2.3 中间 代码 生成 优化 通过 偏移 访问 素和 结构 成员

第5.2.3节 通过“偏移”访问数组元素和结构体成员

在上一节小节,我们举例介绍了对“数组元素和结构体成员”的访问,我们采用的是“基地址+偏移”的模式来计算其内存单元的地址。对于数组元素arr2[i][2]来说,数组索引值i为变量,对应的地址要表达为“基地址+常量偏移+非常量偏移”;对于结构体成员dt.b来说,其地址可表达为“基地址+常量偏移”。下面,我们还是结合一个简单的例子来说明相关概念,如图5.2.9所示,第1至14行给出了一个简单的C程序,第16至30为UCC编译器生成的中间代码,第33至46行是UCC编译器生成的汇编代码,而第49至58则是GCC编译器生成的汇编代码。由于第10行的arr[i]含有“非常量偏移”,C编译器需要生成代码来计算这些偏移,再与数组首地址进行相加。在汇编代码中,用于寻址的指令相当灵活,对于“arr[i]=30;”来说,GCC所生成的代码就与UCC不同,如图5.2.9第50至51行所示。UCC编译器采用的是形如第34至38行的指令,第34至35行用于计算“非常量偏移”,即i*4,通过把i左移2位来实现,第36行通过leal指令来取数组arr的首地址,第37行进行把基地址和偏移相加,所得结果存于寄存器ecx,之后通过第38行的寄存器间接寻址就可完成赋值。第19行的中间代码“t1:&arr”对应第36行的汇编代码 “leal arr,%ecx”。我们还发现,第11行的C代码“arr[2] = 50;”对应的汇编代码为第40行的“movl $50,arr+8”。在汇编代码中出现的符号arr可看成是一个地址常量,该movl指令把常数50送到(arr+8)所对应的内存单元中。

\

?

图5.2.9 对数组元素的寻址

在中间代码层次,一个符号对象struct symbol(或其“子类”对象,例如struct variableSymbol)可作为三地址码中的目的操作数或源操作数。UCC编译器在为“抽象语法树上的arr结点”生成中间代码时,并没有考虑其所处的上下文,为了能生成形如第36行的汇编指令,UCC编译器需要产生一条形如第19行的中间代码“t1:&arr;”,其中的临时变量t1存放了数组arr的首地址。虽然数组中的内容可能会被修改,数组arr的地址在数组生命周期内并不会发生变化,所以&arr可以当作公共子表达式来使用,当我们在第11行遇到另一棵抽象语法子树上的arr结点时,我们就可重用临时变量t1中的值。如果t1对应的寄存器为eax,在汇编层次,我们可为第11行的C语句“arr[2]=50;”生成以下汇编代码。

leal arr, %eax; //取数组arr的地址

addl $8, %eax; //arr[2]在数组arr中的偏移为常量8

movl $50,(%eax); //通过寄存器间接寻址来进行赋值

这些汇编代码可以实现C语句“arr[2] = 50;”所要求的语义,但并不是很高效,我们可用图5.2.9第40行的“movl $50,arr+8”来实现一样的功能。在知道基地址和偏移的前提下,我们可以通过UCC编译器中的函数Offset,来产生“访问数组元素或结构体成员”的中间代码,如图5.2.9第18至20行所示;而函数AddressOf则可以生成第19行的“t1:&arr;”的取地址指令。

函数Offset的代码如图5.2.10所示,当C程序员访问数组元素或结构体成员时,第2行的参数addr是数组元素或结构体成员的基地址,参数voff是“非常量偏移VariableOffset”。当访问图5.2.9第12行的结构体成员dt.num时,由于结构体成员dt.num在结构体对象dt中的偏移是固定的,此时voff参数为NULL。但在访问dt.num[i]时,数组元素dt.num[i]在数组dt.num中的偏移为(i*4),这不是常量,此时voff不为NULL,而访问arr[i]时voff也不为NULL。第2行的另一个参数coff代表“常量偏移ConstOffset”,访问图5.2.9第11行的arr[2]时,coff的值为2*sizeof(int),即8。图5.2.10第3至8行的代码用于产生代码,进行基地址、非常量偏移和“常量偏移”这三者的加法运算,得到地址后,再由第7行的Deref进行“间接寻址操作”,这样我们就可以为“arr[i] = 30;”生成形如图5.2.9第18至21行的中间代码。当C程序员要访问arr[2]时,此时图5.2.10第8行注释中的t1就对应参数addr,voff为NULL,而coff的值为8,为了能产生形如图5.2.9第40行的汇编代码“movl $50,arr+8”,而不是生成“leal arr, %eax; addl $8, %eax; movl $50,(%eax);”这3条低效的代码,我们在第11行调用CreateOffset函数创建了一个新的符号对象,用来在中间代码层次表示形如arr[8]这样的符号。对于图5.2.10第13至14注释中所示的代码而言,ptr是指向int[4]数组的指针,C程序员通过(*ptr)[2]来访问数组元素时,UCC编译器会在语义检查CheckUnaryExpression时构造成一棵形如([] ([] ptr 0) 8)的语法树,在翻译这个语法树时,我们会计算出基地址为ptr,而偏移为8,此时我们把这两者相加,再通过间接寻址才能访问到相应的数组元素,第15行调用的Deref完成此功能。如果我们把C程序员写的(*ptr)[2],错误地表示为中间代码层次的符号ptr[8],则最终生成的汇编代码会是“movl $50,ptr+8”。假设数组arr的首地址是10000,而全局变量ptr的地址为20000,则变量ptr中的内容为10000,在此movl指令中,ptr是地址常数20000,该movl指令会把常数50传送到地址20008对应的内存单元。不过,按C的语义,(*ptr)[2]实际应访问C的数组元素arr[2],数组元素arr[2]的地址为10008,因此“movl $50,ptr+8”是条错误的指令。所以,在中间代码层次,我们不可用符号ptr[8]来表示相应的数组元素(*ptr)[2],而是要生成的形如“t5 : ptr + 8; *t5 = 60;”的代码,这些中间代码是通过图5.2.10第15行的函数Deref来产生,Deref是Dereference的缩写,表示“提领操作”,也有译为“解引用”,实际上进行的操作是“间接寻址”。

\

?

图5.2.10 Offset()

当要访问结构体成员dt.num,或者要访问的数组元素不存在“非常量偏移”(例如arr[2])时,我们可用第18至39行的CreateOffset来为其创建一个符号对象,第18行的base代表基地址,第19行的coff代表“常量偏移”。如果偏移coff为0,比如当我们要初始化图5.2.10第21行注释中的局部变量d和dt时,第22行的条件就会成立,此时我们直接返回base即可。但是如果我们要访问的是dt.a时,按图5.2.9第4至7行的结构体定义,dt.a在对象dt中的偏移为0,但dt.a和dt的类型不一样,因此我们需要为dt.a创建一个新的符号对象,而不能使用和dt一样的符号对象,此时第22行的条件就不成立。

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C语言逆序字符串数组 下一篇Objective C设计模式之外观模式fa..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: