前言
第7章 语法制导的语义计算
语义分析是上下文有关的,目前较为常见的是用属性文法来描述程序语言语义,并采用语法制导翻译的方法完成对语法成分的翻译工作。
属性文法
属性 描述文法符号的类型、值等有关的一些信息,它可以被计算或传递。
语义动作 指产生式相关联的指定操作
条件谓词 指产生式关联的接受条件,或者根据该条件谓词决定做什么语义动作
语义规则集 通常是产生式关联的一组语义规则,每个语义规则可以是一个语义动作或条件谓词。
属性att可以与某个文法符号a关联,用a.att来表示这种关联
现有一文法:
E→T1+T2∣T1&&T2
T→num∣true∣false
将上面的文法描述为类型检查的属性文法:
E→T1+T2{T1.type=int&&T2.type=int}
E→T1&&T2{T1.type=bool&&T2.type=bool}
T→num{T.type=int}
T→true{T.type=bool}
T→false{T.type=bool}
综合属性和继承属性
对关联于产生式A→α的语义动作b:=f(c1,c2,...,ck),如果b是A的某个属性,则b是A的一个综合属性。综合属性是自底向上传递信息。
对关联于产生式A→α的语义动作b:=f(c1,c2,...,ck),如果b是产生式右边某个文法符号X的某个属性,则b是A的一个继承属性。继承属性是自顶向下传递信息。
带标注语法分析树,即在语法树的基础上,将原来的非终结符结点修改为综合属性的赋值。
下面是一个简单表达式文法G[S]的一个仅含综合属性的属性文法(开始符号为S)
S→E{print(E.val)}
E→E1+T{E.val:=E1.val+T.val}
E→T{E.val:=T.val}
T→T1∗F{T.val:=T1.val×F.val}
T→F{T.val:=F.val}
F→(E){F.val:=E.val}
F→d{F.val:=d.lexval}
其中d.lexval表示数值,E.val,T.val,F.val都为综合属性
现在要给表达式3∗(5+4)构造语法树和带标注语法分析树:


下面则是一个包含综合属性、继承属性的属性文法:
E→TR{R.in:=T.val;E.val:=R.val}
R→+TR1{R1.in:=R.in+T.val;R.val:=R1.val}
R→−TR1{R1.in:=R.in−T.val;R.val:=R1.val}
R→ε{R.val:=R.in}
T→num{T.val:=lexval(num)}
其中lexval(num)表示从词法分析程序得到的常数值。
可见E.val,T.val,R.val都为综合属性,R.in为继承属性
现在要给表达式3+4−5构造语法树和带标注语法分析树:


这一章可能的考点
- 了解综合属性和继承属性。已知属性文法和输入符号串,构建语法树和带标注语法分析树。
第8章 静态语义分析和中间代码生成
中间代码生成
中间代码 一种介于源语言和目标语言的中间语言形式,有:
- 逆波兰表示
- 三元式表示
- 四元式表示
- 树形表示
逆波兰表示
逆波兰表示法即为后缀表示法,而默认我们使用的表达式是中缀表示法
程序设计语言中的表示 |
-----逆波兰表示----- |
a+b |
ab+ |
−a |
a@ |
a+b∗c |
abc∗+ |
(a+b)∗c |
ab+c∗ |
a:=b∗c+b∗d |
abc∗bd∗+:= |
a:=b∗(c+b)∗(−d) |
bcb+∗d@∗:= |
逆波兰式的使用:需使用额外的标识符栈。顺序扫描逆波兰表达式的时候,遇到标识符直接入栈。
遇到运算符时:
- 根据运算符目数,从栈顶取出相应数目的标识符做运算,并把运算结果压栈
- 运算结束时,标识符栈应该只剩下一个元素,且为运算结果
三元式表示
三元式(op,A1,A2)
op为运算符
A1为第一运算对象
A2为第二运算对象
例如a:=b∗c+b∗d表示为:
(1)(∗,b,c)
(2)(∗,b,d)
(3)(+,(1),(2)) 这里用(1)和(2)来表示中间计算结果的显式引用
(4)(:=,(3),a) 这里相当于a:=(3)
而单目运算的−b可以表示成(−,b,/)
树形表示
树形表示和三元式表示非常相似,如a:=b∗c+b∗d表示为:

注意赋值表达式中被赋值对象在树的左孩子节点位置
单目运算−T1直接表示成:

四元式(三地址码)表示
四元式(op,A1,A2,R)
op为运算符
A1为第一运算对象
A2为第二运算对象
R为运算结果
例如a:=b∗c+b∗d的四元式表示:
(1)(∗,b,c,t1)
(2)(∗,b,d,t2)
(3)(+,t1,t2,t3)
(4)(:=,t3,−,a)
和三元式的差别在于,四元式对中间结果的引用必须通过给定的名字(临时变量)
它的三地址码写法为:
t1:=b∗c
t2:=b∗d
t3:=t1∗t2
a:=t3
翻译
布尔表达式的翻译
布尔表达式在程序设计语言中有两个作用:
- 计算逻辑值
- 用于改变控制流语句中的条件表达式
控制流语句包含循环、分支两大类。
通常我们只考虑如下文法生成的布尔表达式:
E→EandE∣EorE∣notE∣idropid∣id∣true∣false
其中rop是关系符,如<=,<,=,>,>=等
布尔运算符的优先顺序为not>and>or
并且and和not服从左结合
布尔表达式的计算有两种方法:
- 计算各部分的真假值,最后计算出整个表达式的值
(1and0)and(0or1)=0and1=0
- 短路法:AandB如果A=0则直接得到0;AorB如果A=1则直接得到1。这种方式若B为一个带返回值的过程调用会引发副作用
布尔表达式翻译成四元式序列,如aorbandnotc的翻译结果为:
(1)t1:=notc
(2)t2:=bandt1
(3)t3:=aort2
条件语句中布尔表达式的翻译
现在有文法:
S→ifEthenS1∣ifEthenS1elseS2∣whileEdoS1
翻译这部分的题目主要是以给定四元式序列,然后填空。
对布尔表达式E=aropb,可以翻译成:
(1)ifaropbgotoE.true
(2)gotoE.false
但此时E.true和E.false的值仍不能被确定。例如:
S→ifEthenS1elseS2
E.true的值为S1的第一条四元式的地址
E.false的值为S2的第一条四元式的地址
ifa<borc<dande>fthenS1elseS2的四元式序列:
(1)ifa<bgotoE.true––––––––
(2)goto(3)–––
(3)ifc<dgoto(5)–––
(4)gotoE.false–––––––––
(5)ife>fgotoE.true––––––––
(6)gotoE.false–––––––––
(7)S1begin...
...
(p−1)...S1end
(p)gotoq–
(p+1)S2begin...
...
(q−1)...S2end
(q)...
在产生出S1和S2的状态块后,才能进行地址回填。上面的E.true应填(7),而E.false应填(p+1)。
为了解决地址回填的问题,需要采用拉链法,把需要回填E.true的所有四元式构造并维护一条真链的链表,把需要回填E.false的所有四元式构造一条假链的链表
对于上面的例子,真链和假链如下图:

其中(5)为真链的链首,(6)为假链的链首。一旦确定S1和S2的地址,就可以沿着链作地址回填
但还有3种情况会使得四元式序列变得十分复杂,这里不讨论:
- 连续的or或连续的and
- 连续的if−elseif−elseif...
- 嵌套条件语句
循环语句中布尔表达式翻译
现需要翻译语句:whilea<bdoifc<dthenX:=Y+Z
100:ifa<bgotoE.true––––––––
101:gotoE.false–––––––––
102:ifc<dgoto104––––
103:goto106––––
104:t1:=Y+Z
105:X:=t1
106:goto100––––
107:...
分析到状态块的开始就可以确认E.true=102,而分析完状态块的结束之后就可以确认E.false=107
for循环语句翻译
现需要翻译语句:forI:=1step1untilYdoX:=X+1
等价于C语言的:for(I=1;I<=Y;++I)X=X+1;
100:I:=1
101:goto103––––
102:I:=I+1
103:ifI<=Ygoto105––––
104:goto108––––
105:T:=X+1
106:X:=T
107:goto102––––
108:...
数组的翻译
对于一个静态的n维数组,要访问其中一个元素,可以使用下标法:
A[i1,i2,...,in]
由于内存是一维连续空间,对于行主数组,比如一个2×3的二维数组,在内存中的布局为:
A[1,1]A[1,2]A[1,3]A[2,1]A[2,2]A[2,3]
现知道数组A的地址为a,那A[i,j]的地址为:
a+(i−1)×3+(j−1)
设B为n维数组B[l1:u1,l2:u2,...,ln:un]
显然di=ui−li+1。令b是数组首元素地址,那么行主数组下B[i1,i2,...,in]的地址D为:
D=b+(i1−l1)d2d3...dn+(i2−l2)d3...dn+(in−1−ln−1)dn+(in−ln)
对式子展开可以提取出式子中的固定部分和变化部分:
D=constPart+varPart
constPart=b−C
C=l1d2d3...dn+l2d3...dn+...+ln−1dn−1+lndn
varPart=i1d2d3...dn+i2d3...dn+...+in−1dn−1+indn
访问数组元素A[i1,i2,...,in]需要产生两组计算数组的四元式:
- 一组计算varPart,存放结果到临时单元T中
- 一组计算constPart,存放结果到另一个临时单元T1中
用T1[T]表示数组元素的地址
变址取数的四元式:(=[],T1[T],−,X),相当于X:=T1[T]
变址存数的四元式:([]=,X1,−,T1[T]),相当于T1[T]:=X
现在有一个10*20的数组A,即d1=10,d2=20。则X:=A[I,J]的四元式序列为:
(∗,I,20,T1)
(+,T1,J,T1)
(−,A,21,T2)
(=[],T2[T1],−,T3)
(:=,T3,−,X)
对应:
varPart=20∗I+J
constPart=A−(1∗20+1)=A−21
这一章可能的考点
- 中间代码的逆波兰表示、树形表示、三元式表示、四元式表示
- 翻译相关,可能会给出代码进行填空
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律