350 likes | 482 Views
第七章 语义分析和中间代码产生. 紧接在词法分析和语法分析之后,编译程序要做的工作就是进行静态语义检查和翻译。 静态语义检查通常包括: 1 )类型检查 2 )控制流检查 3 )一致性检查 4 )相关名字检查 其它如名字的作用域分析等也都是静态语义分析的工作。. 中间 代码. 语法分析器. 静态检查器. 中间代码产生器. 语法分析器. 虽然源程序可以直接翻译为目标语言代码,但是许多编译程序却采用了独立于机器的、复杂性介于源语言和机器语言之间的中间语言。这样的好处是: 1 )便于进行与机器无关的代码优化工作; 2 )使编译程序改变目标机更容易;
E N D
第七章 语义分析和中间代码产生 • 紧接在词法分析和语法分析之后,编译程序要做的工作就是进行静态语义检查和翻译。 • 静态语义检查通常包括: 1)类型检查 2)控制流检查 3)一致性检查 4)相关名字检查 其它如名字的作用域分析等也都是静态语义分析的工作。
中间 代码 语法分析器 静态检查器 中间代码产生器 语法分析器 • 虽然源程序可以直接翻译为目标语言代码,但是许多编译程序却采用了独立于机器的、复杂性介于源语言和机器语言之间的中间语言。这样的好处是: • 1)便于进行与机器无关的代码优化工作; • 2)使编译程序改变目标机更容易; • 3)使编译程序的结构在逻辑上更为简单明确。以中间语言为界面,编译前端和后端的接口更清晰。 • 静态语义检查和中间代码产生在编译程序中的地位如图7.1 图7.1静态检查和中间代码产生的地位
7.1中间语言 • 在6.2.4节中介绍了抽象语法树,它是源程序的中间表示方法之一。在此将介绍其它几种常见的中间语言形式。 • 常见的中间语言有:后缀式(逆波兰表示法),树型表示,三元式和四元式等。
7.1.1逆波兰表示法 • 逆波兰表示法是把运算量(操作数)写在前面,把算符写在后面(后缀),也称后缀表示法。 • 例如:a+b写成ab+, a*b写成ab* .用这种办法表示的表达式称为后缀式。 • 一个表达式E的后缀形式定义: • 1)如果E是一个变量或常量,则E的后缀式是E自身。 • 2)如果E是E1 op E2形式的表达式,这里op是任何二元操作符,则E的后缀式为E1’E2’op ,这里E’和E2’分别为E1和E2的后缀式。 • 3)如果E是(E1)形式的表达式,则E1的后缀式就是E的后缀式。
逆波兰表示法用不着使用括号,根据运算量和算符出现的先后位置,以及每个算符的目数就完全决定一个表达式的分解。逆波兰表示法用不着使用括号,根据运算量和算符出现的先后位置,以及每个算符的目数就完全决定一个表达式的分解。 • 例如: (a+b)*c将被表示成 ab+c* • abc+* 所代表的表达式是a*(b+c) • Ab+cd+* 所代表的表达式是(a+b)*(c+d) • 把一般表达式翻译为后缀式是很容易的。表7.1给出了把表达式翻译为后缀式的语义规则描述,其中E.code表示E后缀形式,op表示任意二元操作符,“||”表示后缀形式的连接。
逆波兰表示法中栈的使用 • 后缀式的计值用栈实现非常方便,一般的计值过程是:自左至右扫描后缀式,每碰到运算量就把它推进栈,每碰到K目算符就把它作用于栈顶部的k个项,并用运算的结果来代替这k个项(运算的结果仅有一项)。
7.1.2图表示法 • 图表示法包括DAG与抽象语法树 • 无循环有向图DAG:与抽象语法树一样,对表达式中的每个子表达式,DAG中都有一个结点。一个内部结点代表一个操作符,它的孩子代表操作数。 • 不同点:在DAG中代表公共子表达式的结点具有多个父结点,而在一棵抽象语法树中公共子表达式被表示为重复的子树。 • 可见P167
7.1.3三地址代码 一般形式 x := y op z • 其中 x, y, z 为变量名、常数或编译产生的临时变量; • Op代表运算符号如定点运算符、浮点运算符、逻辑运算符等等。 • 每个语句的右边只能有一个运算符。例如,源语言表达式x+y*z可被翻译为如下语句序列: T1:=y*z T2:=x+T1 其中T1,T2为编译时产生的临时变量。
之所以称为三地址代码是因为每条语句通常包含三个地址,两个用来表示操作数,一个用来存放结果。对于后面给出的三地址代码中,用户定义的名字在实际实现时将由指乡符号表中的相应名字入口的指针所代替。之所以称为三地址代码是因为每条语句通常包含三个地址,两个用来表示操作数,一个用来存放结果。对于后面给出的三地址代码中,用户定义的名字在实际实现时将由指乡符号表中的相应名字入口的指针所代替。 • 常用的三地址语句种类 1)x := y op z 双目运算 2)x := op y 单目运算 3)x := y 赋值 4)if x relop y goto l 条件转移
其他三地址种类 goto l 无条件转移 param x 实在参数 call p, n (n是参数个数) 过程调用 return x 过程返回 x := y[i] 数组运算 x[i] := y x := &y 指针运算 x := *y *x = y
生成三地址代码时,临时变量的名字对应抽象语法树的内部结点。对于产生式E →E1+E2的左端的非终结符号E而言,它的经过计算得出的值往往放到一个新的临时变量T中。 • 赋值语句id:=E的三地址代码包括:对表达式E求值并置于变量T中,然后进行赋值id.place:=T。如果一个表达式仅有一个单个标示符,例如y,则由y自身保留表达式的值。
表7.3是为赋值语句生成三地址代码的S-属性文法定义。如给定输入a:=b*-c+b*-c。非终结符号S有综合属性S.code,它代表赋值语句S的三地址代码。非终结符号E有如下两个属性:表7.3是为赋值语句生成三地址代码的S-属性文法定义。如给定输入a:=b*-c+b*-c。非终结符号S有综合属性S.code,它代表赋值语句S的三地址代码。非终结符号E有如下两个属性: • 1)E.place表示存放E值的名字; • 2)E.code表示对E求值的三地址语句序列。 • 函数newtemp的功能是,每次调用它时,将返回一个不同临时变量名字,如T1,T2,…。 • 为方便,使用gen(x’:=‘y’+’z)表示生成三地址语句X:=y+z。代替x,y或z出现的表达式在传递给gen时求值,用单引号括起来的运算符或操作数将保留引号里字面的符号。在实际实现中,三地址语句序列往往是被存放到一个输出文件中,而不是将三地址语句序列置入code属性之中。
S → id := E S.code := E.code || gen( id.place':='E.place ) E → E1 + E2 E.place := newtemp; E.code := E1.code || E2.code || gen(E.place':='E1.place'+'E2.place) E → E1 * E2 E.place := newtemp; E.code := E1.code || E2.code || gen(E.place':='E1.place'*'E2.place) 表7.3 对赋值语句产生三地址代码的属性文法
E → - E1 E.place := newtemp; E.code := E1.code || gen(E.place':=0-'E1.place) E → ( E1 ) E.place:= E1.place; E.code:= E1.code E → id E.place:= id.place; E.code:= ' ' E → num E.place:= num.val;E.code:= ' ' 注释: || 表示代码序列的连接
三地址语句可看成中间代码的一种抽象形式。通常有三种表示方法:四元式、三元式、间接三元式。三地址语句可看成中间代码的一种抽象形式。通常有三种表示方法:四元式、三元式、间接三元式。 • 1.四元式 • 四元式是一种比较普遍采用的中间代码形式.四元式的四个组成部分是:算符OP,第一运算量ARG1,第二运算量ARG2以及运算结果RESULT.其中,运算量和运算结果有时指用户自定义的变量,有时指编译程序引进的临时变量。 • 如果OP是一个算术或逻辑算符,则RESULT总是一个新引进的临时变量,它用来存放运算结果。
例如:赋值语句A:=-B*(C+D)的四元式表示。 • 表中的@表示求负运算符。 • 凡只需一个运算符的算符一律规定使用ARG1
2.三元式 • 为了避免把临时变量填入到符号表,三元式是由算符OP,两个操作数域ARG1和ARG2组成。两操作数域或者是指向符号表的指针,或者是指向三元式表的指针。 表达式的三元式: w*x+(y+z) (1) *, w, x (2) +, y, z (3) +, (1), (2) 第三个三元 式中的操作数(1) (2)表示第(1)和第 (2)条三元式的计 算结果。
3.间接三元式 • 因为许多三元式是通过指示器紧密相联的,改动三元式很困难,所以作为中间代码,通常不直接使用三元式.而是另设一张指示器表,它将按运算的先后顺序列出有关三元式在三元式表中的位置。即用一张间接码表辅以三元式表的办法来表示中间代码。这种表示法称为间接三元式。 • 好处:当在代码优化过程中需要调整运算顺序时,只需重新安排间接代码表,无需改动三元式表。
例如:语句x:=(a+b)*c y:=d↑(a+b) 表7.6 间接三元式表示 间接代码 (1) (2) (3) (1) (4) (5)
3.树形表示 • 简单变量或常数的树就是该变量或常数自身。如果表示e1和e2的树为T1和T2,那么e1+e2,e1*e2和-e1的树分别为: * - + T1 T1 T2 T1 T2 • 树形表示是三元式表示的翻版。 • 二目运算对应二叉子树,多目运算对应多叉子树.
例: E:=(a+b)*(c+d) 的各种表示形式: • 逆波兰表示: eab+cd+*:= • 三元式表示: (1)(+,a,b) (2)(+,c,d) (3)(*,(1),(2)) (4)(:=,(3),e) • 四元式表示: (1)(+,a,b,t1) (2)(+,c,d,t2) (3)(*,t1,t2,t3) (4)(:=,t3,--,e) • 树表示: := * e + + a b c d
例 : A + B * ( C - D ) + E / ( C - D ) ^N • 逆波兰: A B C D - * + E C D – N ^ / + • 四元式: • (1) ( - C D T1 ) • (2) ( * B T1 T2) • (3) ( + A T2 T3) • ( - C D T4) • ( ^ T4 N T5) • (6) ( / E T5 T6) • (7) ( + T3 T6 T7) 三元式: (1) ( - C D ) (2) ( * B (1) ) (3) ( + A (2) ) (4) ( - C D ) (5) ( ^ (4) N ) (6) ( / E (5) ) (7) ( + (3) (6) )
例: • 把语句:A:=-(x+y)*(c+d)+(x+y+c)翻译成四元式(一元负先于加减,后于乘除),并将生成的四元式做适当的优化. 解:语句 A:=-(x+y)*(c+d)+(x+y+c)翻译成四元式: 100(+,x,y,T1) 101(+,c,d,T2) 102(*,T1,T2,T3) 103(@,T3,-,T4) 104(+,x,y,T5) 105(+,T5,c,T6) 106(+,T4,T6,T7) 107(:=,T7,-,A) 100 和104 的四元式形式上相同, 所以进行删除公共运算的优化: 100(+,x,y,T1) 101(+,c,d,T2) 102(*,T1,T2,T3) 103(@,T3,-,T4) 104(+,T1,c,T5) 105(+,T4,T5,T6) 106(:=,T6,-,A)
5.布尔表达式的翻译 文法:E->E∧E|E∨E|┐E|(E)|i|i rop i 其中E1和E2是算术式,rop是关系符.运算符的优先级(从高到低)为: ┐, ∧,∨,并假定∧和∨都服从左结合规则。所有关系符的优先级都是相同的,而且高于任何布尔算符,低于任何算术算符,关系符不得结合.
5.布尔表达式的翻译 考虑E->E(1)∨E(2),若E(1)为真则E为真,若E(1)为假,则E(2)必须被计值, E(2)的第一个四元式就是E(2)的假出口. E(2)的真假出口就是整个E的真假出口. E(1).TC E(1)的目标 E(1).FC E(1).TC E(1)的目标 E(1).FC E(2).FC E(2)的目标 E(2)的目标 E(2).FC E(2).TC E(2).TC E(1)∨E(2) E(1)∧E(2)
6.条件语句翻译 对条件语句if E then S1 else S2 中的布尔表达式E,其作用仅在于对S1和 S2的选择。因此,作为转移条件的布尔式E,我们可以赋予它两种“出口”:一是“真”出口,出向S1,一是“假”出口,出向S2. T E的代码 F S1的代码 S2的代码
7.条件循环语句的翻译 条件循环语句while E(1) do S(1)通常被翻译成如图所示的代码结构。布尔式E(1) 的“真”出口出向S(1)代码段的第一个四元式。紧接S(1)代码段之后应产生的“假”出口将导致程序控制离开整个while语句。 T E(1)的代码 F S(1)的代码
8.计数循环语句翻译 计数循环语句通常具有下面的形式: for i:= E(1) step E(2) until E(3) do S(1) 假定E(2) 总是正的,则上述循环语句等价于: i:= E(1) goto OVER; AGAIN: i:= i+E(2); OVER: if i<= E(3) then begin S(1); goto OVER; end;
9.数组元素的翻译 在表达式或赋值语句中若出现数组元素,则在翻译时牵涉到数组元素的地址计算。数组在存储器中的存放方式决定了数组元素的地址计算法,从而也决定了应该产生什么样的中间代码。数组在存储器中的存放方式通常有按行存放和按列存放两种。
10.定义翻译时常用的一些语义变量和语义过程 • Entry(i)是对标识符i查找符号表以获知它在表中位置(入口)的函数过程。 • E.Place是和非终结符E相联系的语义变量,表示存放E值的变量名在符号表的入口或整数码(若此变量是临时变量) • GEN(OP,ARG1,ARG2,RESULT)是一个语义过程,它把四元式(OP,ARG1,ARG2,RESULT)添进四元式表中。 • NXQ指示器,指向下一个将要形成但尚未形成的四元式地址(编号). NXQ的初值为1,每当执行一次 GEN 后, NXQ将自动累增1. • Merg(P1,P2)是把P1和P2 为链首的两条链合并为一条链,并返回合并后的链首值的函数。 • Backpatch(P1,P2)是把P1,P2所链接的每条四元式都填为t的过程。
例: 已知文法G及每个产生式相应的语义子程序: (1) E->i { E.TC:=NXQ; E.FC:=NXQ+1; GEN(jnz,Entry(i),-,0); GEN(j,-,-,0)} (2) E->i(1) rop i(2) {E.TC:=NXQ; E.FC:=NXQ+1; GEN(jrop,Entry(i(1)), Entry(i(2)),0); GEN(j,-,-,0)} (3) E->(E(1)) { E.TC:=E(1).TC; E.FC:=E(1).FC} (4) E->┐E(1) { E.TC:=E(1).FC; E.FC:=E(1).TC} (5) EA->E(1)∧ {Backpatch(E(1).TC,NXQ); EA.FC:=E(1).FC} (6) E->EA E(2) {E.TC:=E(2).TC; E.FC:=Merg(EA.FC, E(2).FC)} (7) E() ->E(1)∨ {Backpatch(E(1).FC,NXQ); E().TC:=E(1).TC} (8) E->E() E(2) {E∨.TC:=Merg(E().TC,E(2).TC);E.FC:=E(2).FC)} 试给出布尔表达式a∧b∨c>=d 的四元式中间代码.
解: a∧b∨c≥d a为T: 100(jnz, a, _ ,0) a为F: 101(j, _, _, 0) 102 (1) E∧b∨c≥d 设四元式序号从100开始, a∧b∨c≥d 分析过程为: (5) EAb∨c≥d b为T: 102(jnz, b, _, 0) b为F: 103(j, _, _, 0) F值拉链: 101 ->103 104 (1) EAE∨c≥d (6) E∨c≥d c≥d为T: 104(j≥, c, d, 0) c≥d为F: 105(j, _, _, 0) T值拉链: 102 ->104 外层反填: T: 106 F: q (7) E()c≥d (2) E()E (8) E
解: 100(jnz, a, _ ,0,102) 101(j, _, _, 104) 102(jnz, b, _, 106) 103(j, _, _, 104) 104(j≥, c, d, 106) 105(j, _, _, q) T: 106 F: q 即: T T T a ∧ b ∨ c ≥ d F F F
T F 例: E1的代码 T E2的代码 F 将下面的语句翻译成四元式序列: While A<C ∧ B<D do if A=1 then C:=C+1 else while A ≤ D do A:=A+2; 解:所求语句的代码结构图. 其中E1,E2,E3分别对应A<C∧B<D,A=1和A ≤ D, 并且关系运算符优先级高。 C:=C+1 T E3的代码 F A:=A+2
解: 该语句的四元式为: 100 (j<,A,C,102) 101 (j,_,_,113) /*E1为F*/ 102 (j<,B,D,104) /*E1为T*/ 103 (j,_,_,113) /*E2为F*/ 104 (j=,A<1,106) /*E2为T*/ 105 (j,_,_,108) /*E2为F*/ 106 (+,C,1,C) /*C:=C+1*/ 107 (j,_,_,112) /*跳过else 后的语句*/ 108 (j≤,A,D,110) /*E3为T*/ 109 (j,_,_,112) /*E3为F*/ 110 (+,A,2,A) /*A:=A+2*/ 111 (j,_,_,108) /*转回内层while语句开始处*/ 112 (j,_,_,100) /*转回外层while语句开始处*/ 113