《中间代码翻译》PPT课件.ppt

上传人:小飞机 文档编号:5456895 上传时间:2023-07-09 格式:PPT 页数:70 大小:313KB
返回 下载 相关 举报
《中间代码翻译》PPT课件.ppt_第1页
第1页 / 共70页
《中间代码翻译》PPT课件.ppt_第2页
第2页 / 共70页
《中间代码翻译》PPT课件.ppt_第3页
第3页 / 共70页
《中间代码翻译》PPT课件.ppt_第4页
第4页 / 共70页
《中间代码翻译》PPT课件.ppt_第5页
第5页 / 共70页
点击查看更多>>
资源描述

《《中间代码翻译》PPT课件.ppt》由会员分享,可在线阅读,更多相关《《中间代码翻译》PPT课件.ppt(70页珍藏版)》请在三一办公上搜索。

1、第五讲 语义分析和中间代码产生,语义分析概述中间语言几种常用语句的翻译符号表,CompilerPrinciples,2,静态语义检查和中间代码产生在编译程序中的位置如图所示:,CompilerPrinciples,3,虽然源程序可以直接翻译为目标语言代码,但是通常编译程序还是采用了独立于机器的、复杂性介于源语言与机器语言之间的中间语言。这样做的好处是:(1)便于进行与机器无关的代码优化;(2)使编译程序改变目标机更容易;(3)使编译程序的结构在逻辑上更为简单明确,以中间语言为界面,编译前端和后端的接口更清晰。,CompilerPrinciples,4,1.语义分析概述,一、语义分析的任务审查每

2、一个语法结构的静态语义,即验证语法正确的结构是否有意义。如:赋值语句:x:=x+y,左边变量类型与右边变量类型是否一致。在语义正确的基础上生成一种中间代码或目标代码。,CompilerPrinciples,5,二、语义分析的范围1.确定类型:确定标识符所关联的数据类型。2.类型检查:按语言的类型规则,检查运算的合法性与运算分量类型的一致性,必要时作类型转换。3.识别含义:根据语言的语义定义(形式或非形式),识别程序中各构造成分组合到一起的含义,并作相应的语义处理(生成中间代码或目标代码)。,CompilerPrinciples,6,4.控制流检查:控制流语句必须转移到合法的地方。如C中,bre

3、ak语句规定跳出最内层的循环或switch语句。5.一致性检查:在很多场合要求对象只能被说明一次。如:pascal语言规定同一个标识符在一个分程序中只能被说明一次等。6.相关名字检查:如:Ada,循环或块可以有一个名字,它出现在这些结构的开头或结尾。编译程序必须检查这两个地方用的名字是否相同。其它:如名字的作用域分析等也是语义分析的工作。,CompilerPrinciples,7,三、语义描述工具和语义分析方法语义描述工具 目前流行:用属性文法作为描述语义的工具。语义分析方法根据描述属性文法的语义规则的方式不同分为:语法制导定义翻译方案,CompilerPrinciples,8,属性文法形式定

4、义:一个属性文法是一个三元组A=(G,V,F)其中:G为一个上下文无关文法;V 表示属性的有穷集合;F表示属性的断言或谓词的有穷集。语义规则在对文法符号属性处理过程中,为文法的每一产生式定义一组属性的计算规则,称为语义规则。,CompilerPrinciples,9,3.语法制导翻译 所谓语法制导翻译是指:对文法中的每个产生式都附加上一个语义动作或语义子程序。伴随着语法分析,每当使用一条产生式进行推导或归约时,就执行相应产生式的语义动作(包括:查填表格,改变变量的求值,诊察与报告错误,生成中间代码等),从而完成预定的翻译工作。,自底向上的语法制导翻译自顶向下的语法制导翻译,CompilerPr

5、inciples,10,2.几种常用的中间语言形式,一、逆波兰表示法 波兰表示是波兰逻辑学家J卢卡西维奇于1929年提出的一种既不须考虑优先关系、又不用括号的一种表示表达式的方法(前缀式),在计算机出现以后,这种表示方法显出了巨大的优越性。后人为了纪念他,就用其祖国名字来命名这种表示方法。现在我们要介绍的刚好是另一种波兰表示形式,称为后缀式,即运算符在后。例:a+b ab+a*(b+c)abc+*-a+b*c abc*+,CompilerPrinciples,11,二、图表示法 这里要介绍的图表示包括DAG与我们前面介绍过的抽象语法树。1.无循环有向图(DAG)DAG与抽象语法树基本上一样,对

6、表达式中的每个子表达式,DAG中都有一个结点。一个内部结点表示一个操作符,它的孩子表示操作数。两者所不同的是,在一个DAG中代表公共子表达式的结点具有多个父结点,而在一棵抽象语法树中公共子表达式被表示为重复的子树。如下例:,CompilerPrinciples,12,assign,a,+,*,*,b,uminus,b,uminus,c,c,a:=b*-c+b*-c的图表示法,抽象语法树,DAG,CompilerPrinciples,13,2.再来看A*B+C*D的树、后缀式,后缀式:AB*CD*+不难看出,后缀式实际上是抽象语法树的线性表示形式(后序表示);,CompilerPrinciple

7、s,14,3.树表示的翻译法:例:产生式 语义动作(1)EE1 op E2 E.val:=NODE(op,E1.val,E2.val)(2)E(E1)E.val:=E1.val(3)E-E1 E.val:=UNARY(E1.val)(4)Ei E.val:=LEAF(i),建立一棵新树,以OP为根,以E1.val,E2.val为左右枝。返回指向树根的指针。,与NODE相似,只不过是单枝。,建立以i.LEXCAL为标志的叶结,回送的是结点i的地址。,CompilerPrinciples,15,三、三元式 1.三元式由三个部分组成:算符:OP 第一运算分量:ARG1 第二运算分量:ARG2 2.各

8、种语句都可表示成一组三元式 例1:OP ARG1 ARG2 x+y*z(1)*y z(2)+x(1)这儿实际实现时x,y,z也都是指示器,指向这些变量在符号表中的位置。算符OP一般用整数编码,而且可以加进一些诸如类型之类的信息。,指示器-指向(1)在表格中的位置,CompilerPrinciples,16,对于一目运算符,我们可以规定只用ARG1,而多目运算符可以用若干条相继的三元式来表示。OP ARG1 ARG2 例2:-x+y*z(1)x-(2)*y z(3)+(1)(2)例3:赋值语句的三元式表示 OP ARG1 ARG2 A:=x+y*z(1)*y z(2)+x(1)(3):=A(2)

9、,CompilerPrinciples,17,树、后缀式与三元式,后缀式:AB*CD*+后缀式实际上是抽象语法树的线性表示形式(后序表示);树是三元式的翻版,每个三元式对应一棵(二叉)子树,最后的三元式对应树根。,CompilerPrinciples,18,3.语法制导产生三元式(1)EE1 op E2 我们用E.val表示一个指示器,它或指向有关符号表的某项,或指向三元式表的某项,于是其语义子程序为:E.val:=TRIP(OP,E1.val,E2.val)这儿TRIP(OP,ARG1,ARG2)是一个语义过程,它产生(OP,ARG1,ARG2)并放入三元式表中,返回三元式在表中的位置。,C

10、ompilerPrinciples,19,(2)Ei E.val:=Entry(i)这儿ENTRY是一个语义过程,它查找i在符号表中的位置。若用LOOKUP(NAME)函数表示对NAME查找符号表,找到则返回表项位置,找不到则返回NULL。于是可如下实现:词法分析器扫描到标识符i时送回(种别码,i值),语法分析器把i放入语义变量i.LEXCAL中,这时就可以调用ENTRY过程:,CompilerPrinciples,20,FUNCTION ENTRY(NAME)BEGIN ENTRY:=LOOKUP(NAME);IF ENTRY=NULL THEN ERROR;END 由于三元式的计算过程跟先

11、后顺序密切相关,这就给优化带来麻烦,所以一般不直接使用三元式。,CompilerPrinciples,21,4.间接三元式 在三元式的基础上附加一张指示器表间接码表,按运算的先后顺序列出有关三元式在三元式表中的位置。这种表示方法称为间接三元式。例:语句X:=(A+B)*C;Y:=D(A+B)的间接三元式,CompilerPrinciples,22,有了间接码表后,一方面相同的三元式就无须重复填进表中,节约了空间;另一方面,当在代码优化过程中需要调整运算顺序时,只需重新安排间接码表,无须改动三元式。当然这样一来,语义规则中应添加产生间接码表的动作,并且向三元式表中每填入一个三元式前,必须先查看一

12、下此式是否已经在表中出现过。,CompilerPrinciples,23,四、四元式 一个四元式是一个带有四个域的记录结构:op,arg1,arg2及result。它实际上就是一条三地址的指令。例:A+B*(C-D)-E/FG的四元式为:OP ARG1 ARG2 RESULT-C D T1*B T1 T2+A T2 T3 F G T4/E T4 T5-T3 T5 T6,CompilerPrinciples,24,规定凡只有一个运算量时只用ARG1。以上的Ti都是临时变量,其处理方法可以像用户自定义变量一样进符号表,也可以直接用某种整数码表示。,赋值语句A:=-B*(C+D),CompilerP

13、rinciples,25,例:将语句:if AVBD then i:=i+1 else i:=i-1 写成四元式。(1)(,B,D,T1)(2)(V,A,T1,T2)(3)(BMZ,T2,_,(7)(4)(+,i,1,T3)(5)(:=,T3,i)(6)(JMP,_,_,(9)(7)(-,I,1,T4)(8)(:=,T4,_,i)(9),三地址代码:(1)T1:=BD(2)T2:=AV T1(3)if T2 goto(7)(4)T3:=i-1(5)i:=T3(6)goto(9)(7)T4:=i+1(8)i:=T4(9),有时将四元式表示成更直观的形式三地址代码三地址代码形式:x:=a op b

14、(赋值形式)与赋值语句的区别:其右边最多只能有一个运算符。,CompilerPrinciples,26,参考书中推荐的三地址指令形式:赋值语句x:=y op z,x:=op y,x:=y无条件转移goto L条件转移if x relop y goto L过程调用param x 和call p,n过程返回 return y索引赋值x:=yi和 xi:=y地址和指针赋值x:=&y,x:=y和x:=y,CompilerPrinciples,27,3.某些语句的四元式及翻译,一、说明语句的翻译 程序语言中的说明语句都是给编译程序提供信息的,诸如类型、维数、每维的界种类等,因此一般不生成目标,只是在编译

15、时把有关信息填入相应表格即可。为局部名字建立符号表条目为它分配存储单元符号表中包含名字的类型和分配给它的存储单元的相对地址等信息,CompilerPrinciples,28,1.简单说明句:用一个基本字来定义一串名字,其语法描述一般为:Dinteger namelistreal namelist namelistnamelist,ii 直接用这种文法来制导翻译有点麻烦,就是只能在把所有名字规约成namelist之后才能把性质登记到符号表中。这就意味着在扫描名字时,应把名字用一个队列(或栈)保存起来,然后再处理。实际上,我们可以对文法稍做改动,就可以免去建立队列或栈的过程。修改文法如下:DD,i

16、integer ireal i,CompilerPrinciples,29,例:integer i1,i2,i3,词法分析后:,int i1,i2,i3,(07,-),(06,i1),(12,-),语法分析:,s5,i2,LR分析器,语义动作:FILL(ENTRY(i2),D1ATT);DATT:=D1ATT DD1,i2,语义动作:FILL(ENTRY(i1),int);DATT:=int Dinteger i1,此处的ENTRY过程参见前面,CompilerPrinciples,30,2.数组说明:遇到数组说明,应把有关信息收集到一个“内情向量”表格之中,每个数组对应一个“内情向量”。例如

17、,arrayl1:u1,l2:u2,ln:un的内情向量为:di=ui-li+1所求地址:D=CONSPART+VARPART CONSPART=a-C C=(l1d2+l2)d3+l3)d4+)+ln-1)dn+ln,维数,类型,数组首址,CompilerPrinciples,31,显然,内情向量的大小完全由维数决定。一般来说,像FORTRAN,ALGOL,PASCAL等语言的程序,其内情向量在编译时完全可以确定了。关于内情向量的填写,编译时完全可以做了,而且是只在编译时使用,故可以把它安排成符号表的一部分,无须带到目标程序运行阶段。但有时为了处理方便,也一起带到目标程序中。对动态数组,则编

18、译时开辟出信息向量表区,同时应有填写信息向量的目标指令,我们可以用如下一个子程序来处理。该程序的入口参数为维数n,界限序列l1,u1,l2,u2,ln,un,以及类型type和数组首址。,CompilerPrinciples,32,BEGIN i:=1;N:=1;C:=0;WHILE i=n DO BEGIN di:=ui-li+1;N:=N*di;C:=C*di+li;把li,ui,di填入内情向量中;i:=i+1 END;申请N个单元的数组空间,令其首址为a;把n,C,type和a填入内情向量中 END;其他记录说明、过程说明的翻译也大同小异,此处不再赘述。,CompilerPrincip

19、les,33,二、赋值语句的翻译 1.简单算术表达式的赋值语句:所谓简单指不考虑数组元素、记录、函数的引用等情况。对这种算术表达式和赋值语句的四元式我们已经介绍过,现在就来看看如何翻译。我们先来讨论所有分量都是相同类型的情况,如都是整型。于是,我们可用如下的二义文法来进行描述:Sid:=E EE1+E2E1*E2(E1)-E1id,CompilerPrinciples,34,语义动作:Sid:=E p:=lookup(id.name);if(p!=nil)then emit(p:=E.place)else error EE1 op E2 E.place:=newtemp;emit(E.plac

20、e:=E1.place op E2.place)E-E1 E.place:=newtemp;emit(E.place:=uminus E1.place)E(E1)E.place:=E1.place Eid p:=lookup(id.name);if(p!=nil)then E.place:=p else error,CompilerPrinciples,35,2.类型转换 实际上我们可以把类型信息反映到运算符中,例如用+i,*i表示定点+、*,用+r,*r表示浮点+、*。有的程序设计语言允许混合运算,有的不允许。如果不允许,则发现有类型不相同的运算分量就应该报错。如果允许,就要进行类型转换。按

21、照惯例,整、实运算要全部转成实型。处理的方法就是:给每个非终结符添加一个类型信息,如我们用E.MODE表示E的类型,其值为r或者i。如此一来,关于EE1 op E2 的语义子程序也应作相应改动,使其在必要时能产生对运算分量进行类型转换的四元式。,CompilerPrinciples,36,例:X:=Y+I*J 其中X,Y为实型,I,J为整型(*i,I,J,T1)(itr,T1,-,T2)加入一个类型转换四元式(+r,Y,T2,T3)(:=,T3,-,X)语义动作:对Ei来说,E.place:=ENTRY(i)现在应加上E.MODE:=MODE(i);而对EE1 op E2,其语义子程序可以用如

22、下流程图来表示:,CompilerPrinciples,37,T1:=newtemp,start,E1.MODE=int&E2.MODE=int,E1.MODE=r&E2.MODE=r,E1.MODE=int,E.MODE:=rT2:=newtempemit(itr,E2.place,-,T2)emit(opr,E1.place,T2,T1),E.MODE:=rT2:=newtempemit(itr,E1.place,-,T2)emit(opr,T2,E2.place,T1),E.MODE:=intemit(opi,E1.place,E2.place,T1),E.MODE:=remit(opr

23、,E1.place,E2.place,T1),end,y,y,y,n,n,n,CompilerPrinciples,38,3.数组元素的引用(1)一般考虑:数组元素的引用主要存在一个下标地址的计算问题,这取决于数组在存储器中的存放方式,故不同的存放方式会产生不同的中间代码。以下我们以按行存放方式加以说明。前面说过,数组元素的地址D=CONSPART+VARPART而CONSPART=a-C,其中 C=(l1d2+l2)d3+l3)d4+)+ln-1)dn+ln 静态数组的信息向量在编译过程中就可填写,动态数组的信息向量要在运行时填。总而言之,待到引用数组元素时,实际上信息向量已经完全填好,因此

24、,我们要计算D是毫无问题的。,CompilerPrinciples,39,于是,我们可以把VARPART=(i1d2+i2)d3+i3)d4+)+in-1)dn+in 计算出来,放入临时变量T中,并用T作“变址器”,把CONSPART=a-C计算出来,放入临时变量T1中,并用T1作“基址”。这样一来,对数组元素的引用可用变址方式进行:D=T1T;进而X:=T1T的四元式可写为(=,T1T,-,X),这儿=是“变址取数”操作。其实还可写成:(=,T1,T,X)。类似地,“变址存数”操作可写成(=,X,-,T1T),即XT1T。,CompilerPrinciples,40,(2)数组元素引用的翻译

25、 首先对包含数组元素的赋值语句给出如下模式的文法:AV:=E Videlist|id elistelist,E|E EE+E|(E)|V 使用该文法计算VARPART不太方便,因为在整个elist的翻译过程中随时都需要知道数组名id的符号表入口,以获得关于id的全部信息。为此我们把变量V的产生式改写:Velist|id elistelist,E|idE,(1)elist:下标表达式串;(2)E中只给出+,可认为代表op;(3)该数组元素的定义是嵌套的,这样一来,每当用elistidE规约时,elist就获得了有关id的信息,CompilerPrinciples,41,于是我们可以把含有数组元素

26、的赋值语句的翻译规则对应每个产生式的语义动作构造出来了,下面还是看个例子先:设有说明array A1:10,1:20,给定赋值语句:(1)X:=AI,J;(2)AI+2,J+1:=M+N;则(1)的四元式序列为:(*,I,20,T)i1d2(+,J,T,T)i1d2+i2=VARPART=T(-,A,21,T1)a-C=CONSPART=T1(=,T1T,-,T2)T2:=T1T(:=,T2,-,X)X:=T2,CompilerPrinciples,42,(2)的四元式序列为:(+,I,2,T2)I+2(+,J,1,T3)J+1(*,T2,20,T)i1d2(+,T3,T,T)i1d2+i2=

27、VARPART(-,A,21,T1)a-C=CONSPART(+,M,N,T4)M+N(=,T4,-,T1T)M+NAI+2,J+1 参考书中给出了具体的翻译模式,此处不再啰嗦。,CompilerPrinciples,43,三、控制流语句的翻译 布尔表达式在高级程序语言中只出现在两个方面:求逻辑值和作为各种控制语句的条件表达式。显然对布尔表达式求值的四元式的翻译,我们完全可以仿照算术表达式的翻译来进行。例如 ABC=D可翻译成如下四元式序列:(=,C,D,T1)(,B,T1,T2)(,A,T2,T3)但是对于控制语句中的条件表达式,我们还必须结合控制语句作进一步的分析。,CompilerPri

28、nciples,44,1.条件语句中布尔表达式的翻译 出现在条件语句if E then S1 else S2 中的布尔表达式E,它的作用仅在于控制对S1或S2的选择,亦即提供“真”“假”出口,所以其值无需一直保留。,to E.true,to E.false,E.true:,E.false:,S.next:,CompilerPrinciples,45,于是我们可以采用一种优化措施来处理,用一种递推的方式来确定真假出口(短路)。如:AB 可理解为 if A then true else B AB 可理解为 if A then B else false A 可理解为 if A then false

29、else true ABC=D呢?根据这样的思考,我们可以把作为控制条件的任何布尔表达式表示成仅含下列三种形式的四元式的序列:(jnz,a,-,p)表示 if a goto p(jrop,x,y,p)表示 if x rop y goto p(j,-,-,p)表示 goto p,CompilerPrinciples,46,例:if ABC=D then S1 else S2 的四元式:1.(jnz,A,-,7)2.(j,-,-,3)3.(jnz,B,-,5)4.(j,-,-,p+1)5.(j=,C,D,7)6.(j,-,-,p+1)7.(S1的四元式序列)p.(j,-,-,q)p+1.(S2的四

30、元式序列)q.,CompilerPrinciples,47,到此为止看起来翻译四元式序列似乎问题不大了,只要把有关描述文法写出,再配上相应的语义动作就可以了。例如:EEE|EE|E|(E)|i|i rop i配上语义动作。但是还有一个相当麻烦的问题需要解决,就是有关转移的四元式的第四部分(result)的转移地址无法填写(如上例中7,5,p+1等),因为在生成该四元式的时候这个地址往往还是未知数。那么,该如何处理呢?,CompilerPrinciples,48,想一下我们编写程序的时候,对于goto L,在L还不知道的情况下是如何处理的呢?我们并没有因为L还不知道就停滞不前,而是先记住该语句的

31、位置而把L处空出来,一直到编写到L时,再回过头来找到goto把L的值填上,那么该如何用算法实现这样一个智能过程呢?那就是“回填(backpatching)”!和栈的使用一样,这也是一种非常巧妙的技巧。它把一个由跳转指令组成的列表以综合属性的形式进行传递。下面结合着“标号”来具体说明一下:,CompilerPrinciples,49,2.标号和无条件转移的翻译(1)对于说明性出现的标号,很容易处理:L:S 当这种语句被处理之后,标号L被称为“定义了”的。也就是,在符号表中,标号L的“地址”栏将登记上语句S的第一个四元式的地址(编号)。(2)对于先定义后应用的无条件转移(向后转移的goto L),

32、也很容易处理。对L查表得到它的定义地址p,就可生成goto L的四元式(j,-,-,p)。,CompilerPrinciples,50,(3)对于先应用后定义的情况(前向转移goto L):拉链返填:把所有以L为转移目标的四元式串在一起。链的首地址放在符号表中L的“地址”栏中。建链的方法:若L尚未在符号表中出现,则把L填入表中,置L的“定义否”为“未”,把nextquad填进L的地址栏中作为新链头。然后产生四元式(j,-,-,0),其中0为链末标志;若L已在符号表出现但“定义否”为“未”,则把它的地址栏的编号q取出,把nextquad填进该栏作新链头,然后产生四元式(j,-,-,q)。一旦标号

33、L定义时,我们将根据这条链回填那些待填转移目标的四元式,直到某个四元式的地址部分为0(链尾)。如下图:,CompilerPrinciples,51,(r)(j,-,-,q),(q)(j,-,-,p),(p)(j,-,-,y),(x)(j,-,-,0),未定义标号的引用链,符号表,四元式,CompilerPrinciples,52,一般而言,假定用下面的产生式来定义带标号语句 Slabel S labeli:那么,当用labeli:进行归纳时,应作如下的语义动作:.若i所指的标示符(假定为L)不在符号表中时,则把它填入,置“类型”为“标号”,“定义否”为“已”,“地址”为nextquad;.若L

34、已在符号表中但“类型”不为“标号”或“定义否”为“已”,则报错;.若L已在符号表中,则把标志“未”改为“已”,然后把地址栏中的链头(记为q)取出,同时把nextquad填在其中,最后执行backpatch(q,nextquad)。,CompilerPrinciples,53,当然,这儿“拉链”和“返填”都要用一个子程序(函数过程)来处理。现在返回到那些无法填写转移地址的四元式的处理上,它完全是按照如上讲的方法进行的。这就要记住每条四元式的编号。因为有“真、假”出口的问题,所以对文法中的非终结符还要引进两个语义值,如E.TC,E.FC。建链在产生四元式的时候就可以做了,而返填必须在标号定义后才能

35、进行。如对 if E then S1 else s2,当扫描了then 之后就可以返填E的“真”出口,因为这时已经知道了S1的第一个四元式的编号了。但E的“假”出口还必须等到开始处理S2时才能返填。,CompilerPrinciples,54,3.循环与分情况语句的翻译(1)循环语句 大多数程序语言中都有如下形式的循环句:Sfor i:=E1 step E2 until E3 do S1 其语义各语言可能有所不同,主要区别在先判断、后执行还是先执行、后判断。按Algol语言的解释:i:=E1;goto over;again:i:=i+E2;over:if i=E3 then begin S1;

36、goto again end;,CompilerPrinciples,55,于是为了产生四元式,描述文法改写如下:F1for i:=E1 F2F1 step E2 F3F2 until E3 SF3 do S1 根据前面的解释,i在几处都用到,故ENTRY(i)必须保留下来,而该文法正是基于这样的考虑而写出来的。于是语义动作也就容易写了:,CompilerPrinciples,56,例如F1for i:=E1 对应的语义动作:(1)产生四元式:emit(:=,E1.place,-,ENTRY(i);(2)保留ENTRY(i):F1.place:=ENTRY(i);(3)因为goto over

37、的转移地址暂时填不上,必须 建链:F1.chain:=nextquad;(4)产生无条件转移指令:emit(j,-,-,0);(5)保留again的地址:F1.quad:=nextquad;注意:在源程序的语句中,并没有again,over这样的标号,因此也就没有进符号表的问题。,CompilerPrinciples,57,F2F1 step E2:(1)again的地址应继续保留:F2.quad:=F1.quad;(2)i的符号表入口也要保留:F2.place:=F1.place;(3)生成i:=i+E2的四元式:emit(+,F1.place,E2.place,F1.place);(4)现

38、在over的地址有了,可以返填了:Backpatch(F1.chain,nextquad);,CompilerPrinciples,58,F3F2 until E3:这是一个简单的条件句,需要注意两个出口的问题。(1)again的地址要继续保留:F3.quad:=F2.quad;(2)为处理真出口,把四元式计数器的当前值记录下来:q:=nextquad;(3)产生四元式:emit(j,F2.place,E3.place,q+2);(4)假出口还不知道,则建链:F3.chain:=nextquad;产生假出口的四元式:emit(j,-,-,0);,CompilerPrinciples,59,SF

39、3 do S1:(1)产生四元式:emit(j,-,-,F3.quad);(2)若S1建链,则也有返填问题:Backpatch(S1.chain,F3.quad)(3)转离循环的转移目标留待处理外层S时再返填,故要保留:S.chain:=F3.chain 用于改变程序控制流的语句还有:goto,break,continue,return等。思考:这些语句应如何处理?,CompilerPrinciples,60,(2)分情况语句 各种语言的分支语句不尽相同(case,switch等),这里我们假定其形式为左下所示:case E of C1:S1;C2:S2;Cn-1:Sn-1;otherwise

40、:Sn end;,T:=E;L1:if TC1 goto L2;S1;goto next;L2:if TC2 goto L3;S2;goto next;L3:Ln:Sn;next:,可翻译为,CompilerPrinciples,61,上述例子说明,在分支语句不是太多的情况下,我们可以将其翻译为一连串的条件转移语句,相对应的语义动作构造起来就比较简单了。当然,也可以采用更有效的方法进行翻译,比如构造一张开关表,并构造一个对E值查找开关表的循环程序。在运行时,这个循环程序就对E值查开关 表,一旦匹配上某个Ci,则 转去执行相应Si;若不匹配 任何一个Ci,则执行Sn。要 注意的是,如果Si不是转

41、移 指令,那么在Si之后应有一 条无条件转移指令,以便能 把程序控制引导到整个case 语句之后的地方。,CompilerPrinciples,62,四、过程调用的翻译 1.过程调用主要解决两个问题:(1)把程序控制转移到子程序(过程段),执行完毕再返回。这个问题很好解决。(2)传递实在参数。我们前面谈到过几种不同的参数传递方式(传名、传值、传地址),它们的语义动作也就有所区别。,CompilerPrinciples,63,例:CALL S(A+B,Z)将被翻译成(1)A+BT(2)par T 第一参数地址(3)par Z 第二参数地址(4)call S 转子指令(5)当通过执行转子指令cal

42、l而进入子程序S之后,S就可根据返回地址(5)寻找到实在参数T,Z的地址,放入自己的形式单元,进行间接访问。,CompilerPrinciples,64,2.翻译 我们考虑过程调用文法及其对应语义动作如下:(1)ElistE 初始化queue,仅包含E.place 为了在处理实在参数的过程中记住每个参数的地址,以便以后把它们排列在call指令之前,我们需要一个队列(queue)来存放这些地址。(2)ElistElist,E 把E.place加入到queue的队尾(3)Scall id(Elist)for 队列queue中的每一项p do emit(par,-,-,p);emit(call,-,

43、-,ENTRY(id),Translation Over,CompilerPrinciples,65,Translation Part over,Examples followed.,CompilerPrinciples,66,How do U think about it?,CompilerPrinciples,67,CompilerPrinciples,68,练习一将下面的语句翻译成四元式序列。while AD do if A=1 then C:=C+1else B:=B+5*A,CompilerPrinciples,69,练习二,Pascal语言有如下语法:var a,b:array1.

44、100 of integer;a:=b允许数组之间直接赋值若a和b是同一类型记录,也允许C语言:数组不行,结构体可以为这种赋值设计一种翻译模式,CompilerPrinciples,70,练习三,/*program1*/#include structchar p1;short int p2;long p3;struct1;main()printf(%d,sizeof(struct1);return(1);,/*program2*/#include structchar p1;long p3;short int p2;struct2;main()printf(%d,sizeof(struct2);return(1);,在VC6.0中运行这两个程序得到的结果分别是8和12,在TC2.0中运行这两个程序得到的结果都是7,解释原因。,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号