Dingnuooo's Notes

Back

编译原理 - 语义分析与中间代码生成Blur image

语义分析是编译程序最实质性的工作,第一次对源程序的语义做出解释,引起源程序的实质变化

语义处理的任务:按照语法分析器识别的语法范畴结构,依据其语义规则进行语义检查和处理,产生相应的中间代码或目标代码

中间代码:介于源语言和目标语言之间的一种代码。引入中间代码的目的:方便生成目标代码,便于优化和移植

语法制导的语义分析#

就是说在语法分析的过程中把语义分析给做了。这块承认没法考大题 所以知道定义和概念就行了 不要死抠细节

文法符号的属性 N.t,N 是文法符号,t 是 N 的属性。文法符号可以是终结符也可以是非终结符,例如终结符至少有值和类型。
属性的值由产生式的语义规则来定义(语义规则就是一段代码,告诉计算机要做什么),语义处理就是要计算出属性的值

可以理解为,每一个文法符号都是一个类,成员变量就是属性,成员函数就是语义规则

非终结符有两类属性

  • 综合属性(synthesized 属性):产生式左部符号的某些属性由候选式中符号的属性和自己的其他属性定义
  • 继承属性(inherited 属性):候选式中符号的某些属性由产生式左侧非终结符好的属性和候选式中其他符号的某些属性定义
  • 简单说就是,综合属性就是左部符号的属性,继承属性就是右部符号的属性

终结符的所有属性都是综合属性

语法制导定义 SDD#

Syntax-Directed Definition

利用属性文法。属性文法是一个三元组,二型文法 + 属性集 (所有文法符号加点加属性) + 语义规则

属性翻译文法是上下文无关文法的推广,将每个文法符号和一个语义属性集合相关联,将每个产生式和一组语义规则相关联,这些规则用于计算该产生式中各文法符号的属性值

例如一个计算器的 SDD。注意下标,因为在真正的计算中 E 和 E 是不一样的

计算器SDD示例

再例如乘法运算的 SDD。这里头出现了“右侧符号的属性”,说明是继承属性
乘法运算SDD继承属性示例

再例如变量定义语句。虚属性就是说没有对某个符号的属性做操作,而是做了其他的事情,算是一种广义的计算。
变量定义语句SDD虚属性示例


然后就需要根据语义规则确定各个属性的计算顺序。

定义:依赖图。在语法分析树基础上:继承属性写在符号左边、综合属性写在符号右边(刚好与符号在产生式的位置相反),红色箭头表示“箭头的计算依赖于箭尾”
依赖图中属性位置和依赖箭头示例

注:图中 digit 的属性放右边,因为终结符的所有属性都是综合属性

在执行语法分析的时候,每次砍一个句柄、按照对应关系更新属性就行了

再看一个例子,包含虚属性的处理以及无属性常量 (float) 的处理。虚属性当作综合属性写在右端
含虚属性和float常量的依赖图示例

得到依赖图之后,做拓扑排序得到属性值的计算的顺序

两种特殊的属性文法,可以在语法分析阶段就做完属性值计算,原本的语法树变为称为注释语法分析树:

  • S- 属性文法:所有非终结符只有综合属性。此时只需要后序遍历语法分析树就能算完所有属性。
    S属性文法注释语法树示例

  • L- 属性文法:既有综合属性又有继承属性,但对继承属性有要求:

    • 要么是其直接父节点的继承属性
    • 要么是其左侧兄弟的任何属性
    • 要么是自己的其他继承属性、且自身属性间不存在环

    此时只需要深度优先遍历语法分析树就能算完所有属性
    L属性文法继承属性计算示例一
    L属性文法继承属性计算示例二


SDD 的一个用法是求出句子的抽象语法树。只需要在语义规则当中创建节点并做好连接就行了

抽象语法树中,内部节点为运算符,子节点为运算符相关的运算分量。生成方式:可以通过定义一组语义规则从产生式当中创建出一棵树
抽象语法树构造语义规则示例
语法树到抽象语法树示例
抽象语法树节点连接结果示例

语法制导翻译 SDT#

Syntax-Directed Translation

SDD 只告诉我们这条语义规则与这个产生式相关,但没有告诉我们什么时候计算。所以 SDT 就是把属性计算或语义动作用“{}”嵌入在产生式中,嵌入的位置表示相对应的计算时间。
SDT语义动作嵌入产生式示例

(1) 当 SDD 是 s 属性文法,那么对应的 SDT 中把计算都放在产生式的最后,表示在“归约”的时候做计算。计算的时候只需要在做自下而上分析的时候,在状态栈、符号栈之外多加一个属性栈,每次归约的时候就做属性计算即可。这种翻译方案称为后缀 SDT
后缀SDT属性栈计算示例一
后缀SDT属性栈计算示例二

(2) 当 SDD 是 L 属性文法,那么对应的 SDT 中:继承属性的语义动作放在符号出现之前,多个继承属性动作按拓扑排序排列,分析开始前执行语义动作;综合属性的语义动作放在产生式最后,分析完成后执行语义动作。计算的时候做自上而下分析。

自上而下分析要消除左递归。消除方法:

  • 如果语义动作不涉及属性计算,则直接按以前的方法消除左递归
    无属性语义动作消除左递归示例
  • 如果语义动作涉及属性计算:完全没看懂
    含属性语义动作消除左递归示例

一个例子(这个是会考的,比如说写一个矩阵乘法)。L1L2L3 就是三个位置标签,就是规定 goto 到的位置。<expr>.true 或者<expr>.false 都是标签,不是值
布尔表达式标签翻译SDT示例
布尔表达式标签翻译结果示例

符号表与类型检查#

程序内置的叫做固定单词(关键字、运算符、代码界限),用户定义的变量名之类的叫做标识符

符号表:存放源程序中有关于标识符属性信息的数据结构。属性信息比如变量名、类型、(数组)大小、(函数)参数等。

类型检查没写

中间代码生成#

中间语言#

主要有抽象语法树、逆波兰式、四元式。

语法树去除非终结符和无语义的终结符之后变为抽象语法树,后序遍历线性化之后变为逆波兰式,写成单步表示变为四元式

逆波兰式就是后缀表达式。J 是 jump 的意思 J_T 是跳到 true 的意思
逆波兰式和四元式中间语言示例
关于 if 跳转,当 if 里头的代码执行完,要跳转出整个 if 块
if语句跳转四元式示例一
if语句跳转四元式示例二

三元式:操作符、第一操作数、第二操作数
四元式:操作符、第一操作数、第二操作数、运算结果

三元式中,算式的标号也可以作为结果直接引用,例如下面第二条式子就是把 A 和第一条式子的结果相加
三元式引用临时结果示例
三元式序列示例

间接三元式:新建一个间接码表代表三元式的执行顺序,可以把重复的部分进行合并
间接三元式间接码表示例

四元式:增加一个位置用于存储运算结果
四元式结构示例


说明语句的翻译#

变量名信息,不产生目标代码,供语义检查(是否重复定义)和存储空间分配。处理方式:填入符号表即可有三种

常量定义语句:const pi=3.14
常量定义语句文法示例
常量定义语句语义规则示例
其 SDT 如下
常量定义语句SDT示例


简单说明语句。表中 I 是 int、R 是 real,整型和实型
简单说明语句文法示例
简单说明语句语义规则示例


复合类型说明语句(主要是 struct)
复合类型说明语句文法示例
复合类型说明语句语义规则示例

赋值语句和表达式的翻译#

赋值语句文法 AV=EA\to V=E
SDT:先计算 E.code(因为值可以是一个表达式),然后检查等号左右的类型相容性以及转换,最后复制
赋值语句翻译SDT示例


表达式文法 EE op Eop EidE\to E\ op\ E\mid op\ E\mid id
SDT:
表达式翻译SDT示例


逻辑表达。
逻辑表达式翻译示例

控制流类语句翻译#

打破程序执行顺序,引起程序执行发生跳转这类的重点是跳转目标标记(也就是之前说的 label)
控制流语句跳转标签示例

跳转语句的四元式

  • 无条件:(J, , , pos)
  • 有条件:
    • 条件单独写一个四元式,例如 (>,A,B,T)
    • 当条件为真的时候跳转:(J_t, T, , pos) jump true
    • 当条件为假的时候跳转:(J_f, T, , pos)

第一类:goto 语句。

语句标号表:标号名 + flag + addr,flag=1 时表示是标号的定义 A: S1; (定义性出现),0 是表示是标号的使用 goto A; (使用性出现),addr 表示 S1 的第一条代码的地址或编号

拉链 - 返填技术:为了解决“出现 goto 语句的时候还不知道标记在什么位置”的情况。例如

...
goto 20;
...
goto 20;
...
20: c=a*b;
...
plaintext

拉链返填的意思就是,将跳转位置写成一个链表,每次遇到一个 goto 就把 jump 位置设置成上一次遇到 goto 的位置(第一次遇见的时候位置为 0 表示链尾),直到遇见标记定义位置的时候,然后开始返填,直到填到链头。
goto语句拉链返填示例


第二类:if 语句。当条件当中有 and or 的时候,可以通过 Jt 和 Jf,这样只需要判断一部分式子就行了
if语句短路跳转翻译示例

其他:
纯 if:纯if语句四元式模板
if else:if-else语句四元式模板


第三类:while 循环
while循环四元式模板


第四类:for 循环
for循环四元式模板
一个 for 总共有四个标签。第一个在条件式前面,一轮循环结束后回来判断用的;第二个在循环体的第一行代码,当条件判断为真的时候跳到这里;第三个在循环体外面,当条件判断为假的时候跳到这里;第四个标签在更新循环变量前面,循环体结束之后立刻执行的,执行完回到第一个标签

例题:两层 for 一层 if
两层for一层if例题源码
两层for一层if四元式翻译结果

数组语句的翻译#

先填符号表,再填信息向量表。信息向量表:维数、第一维大小、……、第 n 维大小、体积、计算数元地址不变部分 C、数组第一个元素的地址。

a[m][n] 按行存储和按列存储怎么区分:看在引用的时候乘以的是什么,如果乘以的是列数就是按行存储的(因为一次跳过了全部的 n 列也即一行),如果乘以的是行数那就是按列存储的(因为一次跳过了全部的 m 行也就是一列)

多维数组:int a[d1][d2]...[dn],则

a[i1][i2]...[in]=a0+(i1 x d2 x d3 x ... x dn)
				   +(i2 x d3 x ... x dn)
				   +...
				   +i(n-1) x dn
				   +in) x width
plaintext

计算数元地址不变部分 C:如果不是从 0 存起而是从 1 存起,那么上述式子当中每一个 ik 都要减去 1,展开后会多出来一个不含 i、只含 d 的式子,这个式子就是那个 C,剩下的部分就是 V(可变部分)。为什么叫元地址不变:因为这一块不含 i,编译的时候就能计算出来。这样寻址的公式就变成了 a0 + V - C

四元式:
数组元素地址计算四元式示例

拓展:动态数组 malloc 之类的,编译的时候只分配信息向量表所占据的空间,具体信息都得等执行的 malloc 的时候才能填写

函数的翻译#

函数调用翻译示例
语义处理:检查调用的函数火锅是否已经定义,参数类型和数量是否一致

四元式:call f 1 t3 ,用一个参数调用 f,结果存到 t3

编译原理 - 语义分析与中间代码生成
https://dingnuooo.top/blog/compiler/sem-analysis
Author Dingnuooo
Published at June 29, 2026
Comment seems to stuck. Try to refresh?✨