编译原理(十五)——中间代码生成(3)

一、过程调用和函数调用的中间代码

1. 背景信息

  1. 形参种类:
    值参、变量参数、函数参数
  2. 需要的信息:
    形参的种类、传送的内容、偏移、传送的个数、函数类型(实在函数、形式函数)
  3. 过程调用和函数调用的语法形式
   ProcFunCall -> id (E1, …… , En)

2. 中间代码结构

在这里插入图片描述

  1. 首先生成参数E1,E2,E3…的中间代码
  2. 然后将实参的计算结果传递到行参中,后面的ti就是前面实参通过中间代码生成的结果
  3. 最后通过四元式(CALL,<f>,true/false,Result)定位到具体id对应的函数体部分执行,

关于动态确定转向地址的解释:比如函数的行参是另一个函数function f(G:func):int return{G(1)+G(2)}对于G(1)这个形式的函数调用,G的地址是动态确定的,因为当前不知道具体哪个函数G会被真正的传入function f中作为参数,所以是动态确定的。

3. 过程调用和函数调用的中间代码

Gem目前的情况:
知道参数个数在Gem中取出参数id和前面的压入的语义信息中的中间代码是否相同
(En.typ,En语义信息)
...
(E1.typ,E1语义信息)
(-,id)

要检查的语义错误:

  1. id是不是函数名
  2. 每个实参Ei和形参Xi的类型和种类方面是否匹配
  3. 实参个数和行参个数是否相同

假设有实在函数调用 f(X+1,Y),并且 x 是一般整型变量,Y 是变参整型变量,f函数名,同时假定 f 的两个形参第一个是值参、整数类型,第二个是变参、整数类型,则对应的中间代码如下:

(+,X,1,t1)
(ValACT,t1,Offset1,1)//如果是值参=ValACT
(VarACT,Y,Offset1+1,1)//如果是变参(地址)=VarACT
(CALL,f,true,t2)
//Offset1和Offset1+1分别代表函数f的第1、2个参数的偏移量

4. 过程调用和函数调用的动作文法

这里是用自底向上的分析来添加的动作文法,由于自底向上的动作文法只能添加到尾部,为了同样实现在“任意位置插入”,通过一个间接推导的方式实现:
Recall->ID(visit)
ID->id # 语义动作
原:Recall->id (visit) 希望在id后添加语义动作

<ProcFunCall> -> M(List) #send_turn 
M -> id #push1  
List -> E #nextlist
List -> List,E #nextlist
  1. #push1的语义动作:将id压入Sem栈,参数计数器i为0
  2. #nextlist的语义动作:E已经在栈中,参数计数器累加,i++
  3. #send_turn的语义动作:
    a. 取出id:sem(s-i-1)的所有语义信息;
    b. 依此检查形、实参个数是否一致,检查形、实参类型是否相容;
    c. 产生送实参信息到形参信息的ValAct/VarAct中间代码;
    d. 根据f是实在过程(函数)名或形式过程(函数)名产生相应的CALL代码;
    e. 删除当前过程 ⁄ 函数调用语句所占的i+1个语义栈单元,如果f是函数,则把返回值的类型和语义信息压入Sem栈。

例题:
x + f (H(10), g(Y))
【其中:x是整型变量,H为返回值是整型的形参函数名,H的 形参为整型值参,f,g为返回值是整型的实在函数名,f的参数均为整型值参,g的参数为变参。】

//Offset1表示函数H、g以及f的第一个参数的偏移量
(ValACT,10,Offset1,1)
(CALL,H,false,t1)//H是行参函数名,所以是false;f、g是整型的实在函数名所以是true
(VarACT,Y,Offset1,1)
(CALL,g,true,t2)
(ValACT,t1,Offset1,1)
(ValACT,t2,Offset1+1,1)
(CALL,f,true,t3)
(+,x,t3,t4)

二、控制语句的中间代码生成

1. GOTO语句和标号定位的中间代码

goto L的中间代码(JMP,-,-,L)
L:S的中间代码(Label,-,-,L)S.code//S的中间代码

动作文法、代码生成算法:
S -> goto L #goto  
S -> DL: S 
DL -> L #label
对于标号表有定义情况:
  1. #goto 的语义动作
    L是指向标号表中对应的位置。
    Gen(goto,-,-,sem(s-1)) pop(1)
  2. #label 的语义动作
    Gen(label,-,-,sem(s-1)) pop(1)
对于标号表没有定义的情况、且标号无需声明的语言,要在过程中创建标号表ArrayL:

地址回填技术
由于goto语句跳转分为两种情况:向前跳转/向后跳转。向前跳转的时候一般转移地址已知,向后跳转的时候一般跳转地址尚未确定。所以需要先使用当前生成的四元式的标号代替,当知道转移地址的真实值之后再将转移地址填入。

  1. #goto 的语义动作
    (1)查ArrayL,如果没有则产生一条缺欠(需回填)转移地址的中间代码: (JMP , — , — ,— ),并把标号Li、该四元式的地址以及表示该标号为未定位的标记,添加到ArrayL 。若有则: Li是已经定位的了,从ArrayL中取出它的地址LLi,然后产生中间代码 :
( JMP ,,,  LLi )

Li是未定位的,则从ArrayL中取出它的地址LLi,然后产生需回填转移地址的中间代码 :

( JMP ,,, LLi )

ArrayL( Li )的地址填入上述中间代码编号。

具体实现的时候,先知道当前是JMP,但是后面的转移地址还未知,先将后面的转移地址部分用0填充,等到执行到跳转部分可以得出转移地址之后,再生成一个JMP部分为0,但转移地址部分为已知LLi的四元式而后将二者合并

  1. #label的语义动作:
    产生中间代码: ( LABEL , — , — , L), 然后查ArrayL:
    如果没有标号L则把该标号及其相应的语义信息加入中ArrayL,并且标记为已定位;
    如果有标号Li并标为未定位,则往对应的所有四元式回填地址
具体GOTO语句和标号定位中间代码的示例:(为了回填的速度快一点,属于实现方式)

在这里插入图片描述

首先第一个goto语句要转移到的地方L1地址未知,当前标号未定位,内部标号的位置将当前生成的四元式的序号填入

在这里插入图片描述
继续执行下一个goto语句,很自然的将这个生成的四元式的转移地址部分写上ArrayL中内部标号的内容也就是m,然后当前的四元式执行到第n条,再更新ArrayL中的内部标号为n。

在这里插入图片描述
操作同上。
在这里插入图片描述

执行到40语句知道L1转移地址,可以将当前地址更新为LL1,然后将前面第m、n、p语句的四元式中的转移地址部分填入LL1,最后更改ArrayL中的内部标号部分为LL1.
在这里插入图片描述
将之前没有填入具体的转移地址部分的四元式替换为转移地址生成为LL1已经确定的语句。

在这里插入图片描述
最后50语句正常执行。

2. 条件语句的中间代码

在这里插入图片描述
解释说明:
第一个(then,,-,-)判断当前条件是否成立,然后转移到S1还是S2部分
第二个(else,-,-,-)的作用是当满足执行E1的条件成立的时候如果执行S1结束之后需要跳过S2部分才能继续执行,所以这里起到一个转移定位的作用

在这里插入图片描述

条件语句的动作文法:

同样是自底向上,不能直接插入语句的任意部分,所以间接操作一下。
在这里插入图片描述
从LR分析的角度来说,当出现移入-归约冲突的时候采用移入优先的规则
在这里插入图片描述

3. while语句的中间代码

在这里插入图片描述
解释说明:
首先是(while,-,-,-)因为执行完每次循环体之后都要返回到循环的开始部分重新判断条件是否满足;
然后是E判断部分的中间代码:
然后是(do,e.form,-,-)的条件判断是否可以执行
然后执行while的循环体部分代码
最后要有一个定位转移到while位置的四元式(endwhile,-,-,-)

在这里插入图片描述

三、过程/函数声明的中间代码生成

在这里插入图片描述
进入函数之前写一句:(entry,Q,-,-)
函数执行之后写一句:(endproc/endfunction,-,-,-)结束函数或过程体
在这里插入图片描述
函数声明的中间代码主要由函数体的中间代码组成,其中函数体则有语句序列组成,而我们已经知道语句的中间代码及其生成方法,因此只需要考虑补充哪些中间代码。把符号表中与目标代码生成期间需要的有关函数信息以中间代码的形式保存起来,例如函数的入口标号、空间大小、层数信息等。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值