编译原理龙书第二版第六章习题题解(部分)

6.1节

该节知识点,就是两个,DAG和值编码,掌握这两个就行了

DAG(Directed Acyclic Graph,有向无环图),指出了表达式中的公共子表达式。

值编码,存放DAG中结点的记录数组。数组的每一行表示一个记录,也就是一个结点。

6.1.1

为下面的表达式构造DAG

((x+y)-((x+y)*(x-y)))+((x+y)*(x-y))

解:

6.1.2

为下列表达式构造DAG,且指出它们的每个子表达式的值编码。假定+是左结合的。

1)a+b+(a+b)

值编码

1ida 
2idb 
3+12
4+33

2)a+b+a+b

1ida 
2idb 
3+12
4+31
5+42

3)a+a+(a+a+a+(a+a+a+a))

1ida 
2+11
3+21
4+31
5+34
6+25

6.2节

这一节需要理解4个点,即抽象语法树、四元式序列、三元式序列、间接三元师序列。

抽象语法树:略

四元式序列:有四个字段,op、arg1、arg2、result,例如x=y+z,其中op字段放入+,arg1为y,arg2为z,result为x。

但是需要注意一些特例:

①x = minus y的单目运算符指令和赋值指令x=y是不使用arg2。且对于x=y的情况,op是=

②像param这样的运算既不使用arg2,也不使用result(下面有例题会涉及到)

③条件或非条件转移指令将目标标号放入result字段。

三元式序列:只有三个字段,op,arg1,arg2。其中四元式的result主要用于临时变量名。而三元式中用运算的位置来表示结果。

间接三元式序列:相对与三元式,包含了一个指向三元式的指针的列表。

6.2.1

将算术表达式a+-(b+c)翻译成

1)抽象语法树

2)四元式序列

 oparg1arg2result
0+bct1
1minust1 t2
2+at2t3

3)三元式序列

 oparg1arg2
0+bc
1minus(0) 
2+a(1)

4)间接三元式序列

 instruction
0(0)
1(1)
2(2)
 oparg1arg2
0+bc
1minus(0) 
2+a(1)

6.2.2

对下列赋值语句重复练习6.2.1

1)a=b[i]+c[j]

四元式

 oparg1arg2result
0=[]bit1
1=[]c

j

t2
2+t1t2t3
3=t3 a

三元式

 oparg1arg2
0=[]bi
1=[]c

j

2+(0)(1)
3=a(2)

间接三元式

 instruction
0(0)
1(1)
2(2)
3(3)
 oparg1arg2
0=[]bi
1=[]c

j

2+(0)(1)
3=a(2)

2)a[i]=b*c-b*d

四元式

 oparg1arg2result
0*bct1
1*bdt2
2-t1t2t3
3[]=ait4
4=t3 t4

三元式

 oparg1arg2
0*bc
1*bd
2-(0)(1)
3[]=ai
4=(3)(2)

间接三元式

 instruction
0(0)
1(1)
2(2)
3(3)
4(4)
 oparg1arg2
0*bc
1*bd
2-(0)(1)
3[]=ai
4=(3)(2)

3)x=f(y+1)+2

四元式

 oparg1arg2result
0+y1t1
1paramt1  
2callf1t2
3+t22t3
4=t3 x

三元式

 oparg1arg2
0+y1
1param(0) 
2callf1
3+(2)2
4=x(3)

间接三元式

 instruction
0(0)
1(1)
2(2)
3(3)
4(4)
 oparg1arg2
0+y1
1param(0) 
2callf1
3+(2)2
4=x(3)

6.3节

6.3.1

确定下列声明序列中各个标识符的类型和相对地址。

float x;
record {float x; float y;} p;
record {int tag; float x; float y;} q;

SDT

S ->                  {top = new Evn(); offset = 0;}
     D 
D -> T id;            {top.put(id.lexeme, T.type, offset);
                       offset += T.width}
     D1
D -> ε
T -> int              {T.type = interget; T.width = 4;}
T -> float            {T.type = float; T.width = 8;}
T -> record '{'
                      {Evn.push(top), top = new Evn();
                       Stack.push(offset), offset = 0;}
     D '}'            {T.type = record(top); T.width = offset;
                       top = Evn.top(); offset = Stack.pop();}

标识符类型和相对地址

lineidtypeoffsetEvn
1)xfloat01
2)xfloat02
2)yfloat82
2)precord()81
3)tagint03
3)xfloat43
3)yfloat123
3)qrecord()241

6.4节

6.4.1

向图 6-19 的翻译方案中加入对应于下列产生式的规则:

1)E -> E1 * E2

2)E -> +E1

产生式语义规则
E -> E1 * E2

E.addr = new Temp()

E.code = E1.code || E2.code ||
                     gen(E.addr '=' E1.addr '*' E2.addr)

E->+E1

E.addr = E1.addr

E.code = E1.code

6.4.2

使用图 6-20 的增量式翻译方案重复练习 6.4.1

产生式语义规则
E -> E1 * E2

{E.addr = new Temp();

gen(E.addr '=' E1.addr '*' E2.addr);}

E->+E1

{E.addr = E1.addr;}

6.4.3

使用图 6-22 的翻译方案来翻译下列赋值语句:

1)x = a[i] + b[j]

三地址代码

t_1 = i * awidth
t_2 = a[t_1]
t_3 = j * bwidth
t_4 = b[t_3]
t_5 = t_2 + t_4
x = t_5

注释语法分析树

2)x = a[i][j]+ b[i][j]

三地址代码

t_1 = i * ai_width
t_2 = j * aj_width
t_3 = t_1 + t_2
t_4 = a[t_3]
t_5 = i * bi_width
t_6 = j * bj_width
t_7 = t_5 + t_6
t_8 = b[t_7]
t_9 = t_4 + t_8
x = t_9

注释语法分析树

6.4.4

修改图 6-22 的翻译方案,使之适合 Fortran 风格的数据引用,也就是说 n 维数组的引用为 id[E1, E2, …, En]

需修改L产生式

若a表示一个i*j的数组,单元数宽度为k,

a.type = array(i, array(j, k))
a.type.length = i
a.type.elem = array(j, k)

L->id[A]

{ L.addr = A.addr; 
  global.array = top.get(id.lexeme); }

//数组名字就是id的值

A->E

{ A.array = global.array;
  A.type = A.array.type.elem;
  A.addr = new Temp();
  gen(A.addr '=' E.addr '*' A.type.width; }

//简单理解就是数组名仍然是全局的那个,新的地址等于E乘类型长度(这个类型长度其实是改为后面一位的类型长度)

A->A1,E

{ A.array = A1.array;
  A.type = A1.type.elem;
  t = new Temp();
  A.addr = new Temp();
  gen(t '=' E.addr '*' A.type.length);
  gen(A.addr '=' A1.addr '+' t); }

//简单理解就是A的地址是A1+E*数组A的第一个元素的值。关于array,就类似于数组名称还是继承

6.4.5

将公式 6.7 推广到多维数据上,并指出哪些值可以被存放到符号表中并用来计算偏移量。考虑下列情况:

1)一个二维数组 A,按行存放。第一维的下标从 l1 到 h1,第二维的下标从 l2 到 h2。单个数组元素的宽度为 w。

2)其他条件和 1 相同,但是采用按列存放方式。

3)一个 k 维数组 A,按行存放,元素宽度为 w,第 j 维的下标从 lj 到 hj。

4)其他条件和 3 相同,但是采用按列存放方式。

其实3)和4)就是1)和2)的一般情况。

所以这里直接写3)和4)

3)设ni为第i维数组的元素个数,其值为hi-li+1。

A[i1]]…[ik] = base +( (i1 - l1) * n_2 * … * n_k +… + (i_k-1 - l_k-1) * n_k +(ik - lk)) * w//这里由于k-1是下标,为了好区分,加上_。

4)A[i1]]…[ik] = base+((i1 - l1) +(i2 - l2) * n1 + … +(ik - lk) * n_k-1 * n_k-2 * … * n1 ) * w

6.4.6

一个按行存放的整数数组 A[i, j] 的下标 i 的范围为 1~10,下标 j 的范围为 1~20。每个整数占 4 个字节。假设数组 A 从 0 字节开始存放,请给出下列元素的位置:

1)A[4, 5]

2)A[10,8]

3)A[3, 17]

计算通用公式((i-1)*20+(j-1))*4,那么1)2)3)带进去即可,就不多说了。

6.4.7

假定 A 是按列存放的,重复练习 6.4.6

计算通用公式((i-1)+(j-1)*10)*4,带进去即可

6.4.8

一个按行存放的实数型数组 A[i, j, k] 的下标 i 的范围为 1~4,下标 j 的范围为 0~4,且下标 k 的范围为 5~10。每个实数占 8 个字节。假设数组 A 从 0 字节开始存放,计算下列元素的位置:

1)A[3, 4, 5]

2)A[1, 2, 7]

3)A[4, 3, 9]

计算通用公式((i-1)*5*6+j*6+(k-5))*8

6.4.9

假定 A 是按列存放的,重复练习 6.4.8

计算通用公式((i-1)+j*4+(k-5)*5*4)*8

6.5节

6.5.1

假定图6-26中的函数widen可以处理图6.25a的层次结构中的所有类型,翻译下列表达式。假定c和d是字符型,s和t是短整型,i和j为整数,x是浮点数。

1)x=s+c

t1 = (int) s
t2 = (int) c
t3 = t1 + t2
x = (float) t3

2)i=s+c

t1 = (int) s
t2 = (int) c
t3 = t1 + t2

3)x=(s+c)*(t+d)

t1 = (int) s
t2 = (int) c
t3 = t1 + t2
t4 = (int) t
t5 = (int) d
t6 = t4 + t5
t7 = t3 * t6
x = (float) t7

6.6节

6.6.1

在图6-36的语法制导定义中添加处理下列控制流构造的规则:

1)一个 repeat 语句:repeat S while B

2)一个 for 循环语句:for (S1; B; S2) S3

产生式语义规则
S -> repeat S1 while B

S1.next=newlabel()

B.true=newlabel()

B.false=S.next

S.code=label(B.true)||S1.code

             ||label(S1.next)||B.code

S -> for (S1; B; S2) S3

S1.next=newlabel()

B.true=newlabel()

B.false=S.next

S2.next=S1.next//S1在for循环里其实只执行一次,S2是循环里最后执行的部分

S3.next=newlabel()

S.code=S1.code||lable(S1.next)

             ||B.code||lable(B.true)

             ||S3.code||label(S3.next)

             ||S2.code||gen('goto', S1.next)

6.6.2

现代计算机试图在同一个时刻执行多条指令,其中包括各种分支指令。因此,当计算机投机性地预先执行某个分支,但实际控制流却进入另一个分支时,付出的代价是很大的。因此我们希望尽可能地减少分支数量。请注意,在图 6-35c 中 while 循环语句的实现中,每个迭代有两个分支:一个是从条件 B 进入到循环体中,另一个分支跳转回 B 的代码。基于尽量减少分支的考虑,我们通常更倾向于将 while(B) S 当作 if(B) {repeat S until !(B)} 来实现。给出这种翻译方法的代码布局,并修改图 6-36 中 while 循环语句的规则。

产生式语义规则
S->if(B) {repeat S1 until !(B)}

B.true = newlabel()

B.false = S.next

S1.next=newlabel()

S.code=B.code||label(B.true)

             ||S1.code||label(S1.next)

             ||B.code

6.6.3

假设 C 中存在一个异或运算。按照图 6-37 的风格写出这个运算符的代码生成规则。

B1^B2可以等价于!B1 && B2 || B1 && !B2

产生式语义规则
B->B1^B2

B1.true = newlabel()

B1.false = newlabel()

 

B2.true = B.true

B2.false = B1.true

 

 b3 = newboolean()
 b3.code = B1.code
 b3.true = newlabel()
 b3.false = B.false

 

b4 = newboolean()
b4.code = B2.code

b4.true = B.false
b4.false = B.true

 

S.code = B1.code
                || label(B1.false) || B2.code
                || label(B1.true) || b3.code
                || label(b3.true) || b4.code

6.6.4

使用 6.6.5 节中介绍的避免 goto 语句的翻译方案,翻译下列表达式:

1)if (a==b && c==d || e==f) x == 1

      ifFalse a==b goto L3 
      if c==d goto L2

L3: ifFalse e==f goto L1

L2: x == 1

L1:

2)if (a==b || c==d || e==f) x == 1

      if a==b goto L2
      if c==d goto L2
      ifFalse e==f goto L1

L2: x==1

L1:

3)if (a==b || c==d && e==f) x == 1

        if a==b goto L2

        ifFlase c==d goto L1

        ifFlase e==f goto L1

L2:x==1

L1:

总结一下,一般在多个条件的最后一个或者与条件的第一个需要进行此操作

6.6.6

使用类似于图 6-39 和图 6-40 中的规则,修改图 6-36 和图 6-37 的语义规则,使之允许控制流穿越。

产生式语义规则
S->if(B)S1

B.true=fall

B.false=S1.next=S.next

S.code=B.code||S1.code

S -> if(B) S1 else S2

B.true=fall

B.false=newlabel()

S1.next=S2.next=S.next

S.code=B.code||S1.code

             ||gen('goto' S1.next)||label(B.false)

             ||S2.code

S -> while(B) S1

begin = newlabel()

B.true = fall

B.false = S.next

S1.next = begin

S.code=label(begin)||B.code

             ||S1.code||gen('goto' begin)

S->S1S2

S1.next = fall

S2.next = S.next

S.code=S1.code||S2.code//以上这些就是把原来的所有的B.true都改为等于fall,去掉label(B.true)即可

B->B1 || B2

B1.true= if B.true != fall then B.true else newlabel()//虽然B1为真时B的值必然为真,但是B1.true必须保证控制流跳过B2的代码,而B.true为fall代表如果B为真时控制流穿越B。

B1.false=fall//B1为假,则B的真假由B2决定

B2.true=B.true

B2.false=B.false

B.code=if B.true != fall then B1.code || B2.code

             else B1.code || B2.code || label(B1.true)

B -> B1 && B2 

B1.true=fall

B1.false=if B.false != fall then B.false else newlabel()

B2.true=B.true

B2.false = B.false

B.code=if B.false!= fall then B1.code || B2.code

             else B1.code || B2.code || label(B1.false)

6.7节

6.7.1

使用图 6-43 中的翻译方案翻译下列表达式。给出每个子表达式的 truelist 和 falselist。你可以假设第一条被生成的指令的地址是 100.

1)a==b && (c == d || e==f)

2)(a==b || c==d) || e==f

3)(a==b && c==d) && e==f

6.7.2

1)E3.false

E3.false=i1

2)S2.next

S2.next = i7

3)E4.false

E4.false = i7

4)S1.next

S1.next = i3

5)E2.true 

E2.true = i3

ps:个人感觉这两题即使不学编译原理也能做,就像考验c语言基础语法一样,while的执行步骤与顺序,if的执行规则等等,当然最好是自己画个注释语法树分析一下。

6.7.3

当使用图 6-46 中的翻译方案对图 6-47 进行翻译时,我们为每条语句创建 S.next 列表。一开始是赋值语句 S1, S2, S3,然后逐步处理越来越大的 if 语句,if-else 语句,while 语句和语句块。在图 6-47 中有 5 个这种类型的结构语句:

S4: while (E3) S1

S5: if(E4) S2

S6: 包含 S5 和 S3 的语句块

S7: if(E2) S4 else S6

S8: 整个程序

对于这些结构语句,我们可以通过一个规则用其他的 Sj.next 列表以及程序中的表达式的列表 Ek.true 和 Ek.false 构造出 Si.next。给出计算下列 next 列表的规则:

1)S4.next

S4.next=E3.false=S3.next

2)S5.next

S5.next=E4.false=S2.next

3)S6.next

S6.next=S3.next=S4.next=E3.false

4)S7.next

S7.next=S3.next

5)S8.next

S8.next=E1.false

  • 16
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值