课程
编译原理计算大题解题步骤与示例
编译原理计算大题解题步骤与示例
模块一:高级程序设计语言的语法描述
题型一:文法设计 (Grammar Design)
如何做:
- 明确符号的含义:
- 我们用大写字母(如 , , )代表“中间过程代号”或“组合”。
- 我们用小写字母、数字和符号(如 , , , )代表“最终出现在句子里的具体字符”。
- 用箭头 表示代换,用竖线 表示“或者”。
- 观察字符的匹配规律:
- 如果两个符号的数量是绑定的(例如有 个 就必须有 个 连着),那就在规则的左右两边分别写上 和 ,中间写上它自己,形成一种“包夹”结构。如:。
- 确定最简单的情况(递归结束条件)。如果数量可以是 0,最简情况就是一个空符号 (表示空无一物),如:。
- 如果数量必须大于等于 1,那最简情况就是 ,如:。
- 独立部分拆开写:
- 如果句子的前半部分(如 )和后半部分(如 )互不干扰,数量没有绑定关系,就用两个不同的大写字母拼接,比如先写一行初始规则 ,然后再分别写大写字母 的规则和 的规则。
-
题目:给定语言 ,构造其文法。
-
解答:
- 句子由两部分拼接而成:前半部分是 (数量相等,且至少有一个),后半部分是 (可以有任意个,包括 0 个)。
- 我们设初始组合为大写字母 ,拆成前半部分组合 和后半部分组合 :
- 前半部分 产生相同数量的 和 ,且至少 1 对,代换规则为:
- 后半部分 产生任意个 (包括 0 个,即空字符 ),代换规则为:
- 完整文法为:
-
例题 2:构造一个只能产生奇数(且多位数首位不为 0)的十进制数文法。
-
解答:
- 句子如果是一位数,只能是奇数:,我们用非终结符 表示。
- 句子如果是多位数,首位数字不能是 0,只能是 到 ,我们用非终结符 表示;末位数字必须是奇数,即 ;中间部分可以是任意长度的数字串(包含0,也可以为空),我们用非终结符 表示。
- 定义单个数字非终结符 ,它可以是 0 或者是首位数字 。
- 完整文法定义为:
题型二:最左/最右推导与推导过程判定
如何做:
- 最左推导:从初始大写字母(比如 )开始。在每一步中,只在当前的符号串里找到最靠左边的那个大写字母,查规则把它代换掉,其余的大写字母和符号保持不变。重复这一步,直到符号串里没有任何大写字母。
- 最右推导(规范推导):同理,每一步中只找到最靠右边的那个大写字母,用规则代换掉它,其他部分不动,直到没有大写字母。
- 画代换过程树(语法树):
- 把初始大写字母写在最顶端。
- 每次代换时(如 ),从这个大写字母往下分叉画出分支,依次连上它变出来的符号(如左分叉连 ,中间分叉连 , 右分叉连 )。
- 重复这个过程,直到树的最底端全都是具体的符号(小写字母、数字或符号)。从左到右把树底部的具体符号串起来,应当刚好等于要推导的句子。
-
题目:已知代换规则为: 给出句子 的最左和最右推导,并给出语法树。
-
解答:
- 最左推导(每次换最左边的大写字母):
- 最右推导(每次换最右边的大写字母):
- 语法分析树:
graph TD E1["E"] --> E2["E"] E1 --> P["+"] E1 --> T1["T"] E2 --> T2["T"] T2 --> F1["F"] F1 --> I1["i"] T1 --> T3["T"] T1 --> M["*"] T1 --> F2["F"] T3 --> F3["F"] F3 --> I2["i"] F2 --> I3["i"]
-
例题 2:对于文法 ,给出句子 的最左推导和最右推导。
-
解答:
- 最左推导(每次代换最左侧的非终结符):
- 最右规范推导(每次代换最右侧的非终结符):
题型三:句型、短语、直接短语与句柄判定
如何做:
- 第一步,画出代换树:根据给定的符号串,从初始大写字母开始代换,画出对应的树,使树的底部叶子节点从左到右连起来刚好是给定的符号串。
- 第二步,找短语:树里所有有分叉的代号(即大写字母)都是一个“子树根”。每一个子树根往下看,把从它身上分叉出去的所有底部叶子节点(不管是大写字母还是具体符号)从左到右串成一个子串,这个子串就是该句子的一个短语。
- 第三步,找直接短语:寻找那些“一步就生成叶子”的大写字母(即它的分叉下面全部是树底部的叶子,没有更深的分叉了)。这些一步生成出来的子串就是直接短语。
- 第四步,找句柄:在找出来的所有直接短语里,看哪个位置最靠左边,最左侧的那一个直接短语就是句柄。
-
题目:已知规则如下,证明 是它的一个符号串,并指出所有短语、直接短语与句柄。
-
解答:
- 证明是符号串:存在推导 ,故它是由初始字母推导出来的合法符号串。
- 画出代换树:
graph TD E1["E"] --> E2["E"] E1 --> P["+"] E1 --> T1["T"] T1 --> T2["T"] T1 --> M["*"] T1 --> F["F"] - 分析各分叉代号生成的底部叶子(找短语):
- 以 为根的子树,底部的分叉叶子是 、 和 ,所以 是短语。
- 以 为根的子树(整棵树),底部的分叉叶子是 、、、 和 ,所以 是短语。
- 找直接短语:
- 观察树中,只有 这个节点是一步分叉直接产生了叶子节点 , , ,它没有更深的分支。
- 所以,直接短语只有 。
- 确定句柄:
- 直接短语中,最左边的就是 。
- 句柄是 。
-
例题 2:已知文法 为: 给出句型 的所有短语、直接短语与句柄。
-
解答:
- 最右推导过程:
- 画出语法树:
(注意:右侧的非终结符 未展开,作为叶子节点直接出现在句型中)graph TD T1["T"] --> LP1["("] T1 --> F1["F"] T1 --> RP1[")"] F1 --> T2["T"] F1 --> P["+"] F1 --> F2["F"] F2 --> T3["T (叶)"] T2 --> LP2["("] T2 --> F3["F"] T2 --> RP2[")"] F3 --> T4["T"] T4 --> t["t"] - 寻找短语:
根据语法树,找每一个有子树的非终结符节点,其所有叶子子节点组合成的符号串为短语:
- 以 为根的子树,叶节点为
t,故 是短语。 - 以 为根的子树,叶节点为 (推导为
t),故 是短语。 - 以 为根的子树,叶节点为
(, (推导为t),),故 是短语。 - 以 为根的子树,叶节点为 本身,故 是短语。
- 以 为根的子树,叶节点为 (即 ),故 是短语。
- 以 为根的子树,叶节点为 ,推导为 ,故 是短语。
- 以 (根节点)为根的子树,叶节点为所有,即 ,故 是短语。 去重后,短语集合为:。
- 以 为根的子树,叶节点为
- 寻找直接短语:
找出高度为 2 的子树(即一步推导就全变成叶节点的非终结符):
- 是一步推导,所以 是直接短语。
- 是一步推导(因为 作为叶子节点不再展开),所以 是直接短语。 直接短语集合为:。
- 确定句柄: 句柄是最左边的直接短语。在句型 中,直接短语 的位置比 更靠左,因此句柄是 。
题型四:文法二义性证明
如何做:
- 二义性的含义:一个句子用同一套规则,可以画出两棵不同形状的树(或者写出两个不同的最左代换过程)。
- 证明步骤:
- 挑一个尽可能简单的例子句子,比如带有连加或加乘混合的句子(如 )。
- 第一种画法:按照“先加后乘”的代换逻辑画一棵树。
- 第二种画法:按照“先乘后加”的代换逻辑画另一棵树。
- 展示这两个推导并说明它们不同,即可完成证明。
-
题目:证明规则 是二义的。
-
解答:
- 选择测试句子:。
- 我们可以写出两种不同的最左代换过程:
- 推导方式 1(先算乘法):
- 推导方式 2(先算加法):
- 推导方式 1 对应的树中,乘法位于加法的下方(乘法优先);推导方式 2 对应的树中,加法位于乘法的下方(加法优先)。同一个句子可以生成两棵不同的语法树,因此该规则是二义的。
-
例题 2:证明文法 (经典的 Dangling-Else 悬挂else文法)是二义的。
-
解答:
- 选择测试句子: (代表
if C1 then if C2 then S1 else S2)。 - 我们给出该句子对应的两个不同的最左推导:
- 推导方式 1(
else与最外层的第一个if结合): - 推导方式 2(
else与内层的第二个if结合):
- 推导方式 1(
- 由于同一个句子 存在两个不同的最左推导(可画出两棵不同结构的语法分析树),因此该文法是二义的。
- 选择测试句子: (代表
模块二:词法分析与正规式
题型五:正规式的代数性质与等价性证明
如何做:
- 星号 :代表“可以重复 0 次或任意多次”。
- 括号和竖线 :代表选择(或者)。
- 等价化简公式:
- (分配律,把前面的 分别乘进去)
- (多次重复还是任意次重复)
- (任意次重复等于“什么都没有”或者“先写一个 ,后面再接任意次重复”)
- Arden代换公式:如果你得到了一个关于大写字母 的等式,形状像 (表示 等于 后面接 ,或者等于 ),那么 的唯一解就是 (表示先重复任意次 ,最后接上 )。
-
题目:利用代数规律证明方程 的唯一解是 。
-
解答:
- 我们把 顺着定义逐步展开代入:
- 当我们无限展开下去,前面的 可以重复任意多次(记作 ),最后接上尾巴 。
- 根据 Arden 引理,方程 的唯一解就是 。这里令 ,直接得出唯一解为 。
-
例题 2:利用正规式的恒等式规律,证明恒等式 成立。
-
解答:
- 方法一:展开法(直观理解):
- 左边 展开:
- 右边 展开:
- 展开后的集合形式完全一致,故等式成立。
- 方法二:代数推导法(利用恒等律 或 ):
- 利用分配律及展开性质: 设 ,那么上式可以写为 。
- 根据 Arden 引理(若 ,则其唯一解为 ):
- 这里令 ,。因此该方程的唯一解是 。
- 另一方面,我们将右边 带入上述方程检验: 发现 也是该方程的解。
- 由于解是唯一的,所以必有 ,即 。得证。
- 方法一:展开法(直观理解):
题型六:构造特定语言的正规式
如何做:
- 明确要匹配的具体字符。例如二进制只有
0和1。- 任意个
0和1的组合写成:。- 根据限制进行拼接:
- 如果说“以
01结尾”,说明前面是任意组合,最后强制加01,写成:。- 如果说“必须包含
01”,说明01前面和后面都可以是任意组合,写成:。
-
题目:给出能被 整除的十进制正整数的正规式(不含前导零)。
-
解答:
- 一个十进制正整数能被 5 整除,它的个位数只能是
0或5。 - 如果是个位数,只有
5这一种可能(0不是正整数,若包含 0 可单独列出)。 - 如果是多位数,首位数字不能是
0,只能是1到9的其中一个,记为:。 - 中间的位数可以是任意数字
0到9的组合,记为:。 - 多位数的个位数必须是
0或5,记为:。 - 组合起来,“个位数”或者“多位数”表示为:
- 一个十进制正整数能被 5 整除,它的个位数只能是
-
例题 2:给出二进制数中包含奇数个 1 或奇数个 0 的二进制数串的正规式。
-
解答:
- 第一步:构造包含奇数个 1 的正规式:
- 包含偶数个 1 的正规式(中间可以有任意个 0):.
- 奇数个 1,相当于偶数个 1 后面再跟一个 1,以及任意个 0:
- 第二步:同理构造包含奇数个 0 的正规式:
- 偶数个 0 的正规式:.
- 奇数个 0,相当于偶数个 0 后面再跟一个 0,以及任意个 1:
- 第三步:求“或”关系(并集):
- 将两者用选择符
|连接即可:
- 将两者用选择符
- 第一步:构造包含奇数个 1 的正规式:
题型七:NFA 确定化与 DFA 最小化
如何做:
- 确定化(合并含空箭头的状态):
- 顺着“空字箭头”(无标记的箭头,通常用 表示)一路走到底,能走到的所有状态合并起来,称为一个“状态包”。
- 从初始状态开始,先把初始状态和它走空箭头能走到的状态打包(比如 走空箭头能到 ,打包为 )。
- 对每个状态包,看它整体读入字符 后能跳到哪些状态,再加上这些新状态能走空箭头到达的状态,打包成新包 。
- 重复这个过程,列出转移表。
- 最小化(化简状态):
- 把所有状态分成两大家族:第一家族是“双圈接受状态”,第二家族是“普通状态”。
- 拿出一个家族,看里面的状态在读入字符 后,跳去的状态是否都在同一个家族里。如果在读入 时,有的跳去第一家族,有的跳去第二家族,说明它们“心不齐”,必须把它们分裂成不同的小组。
- 重复这个分裂过程,直到每个小组内的状态读入相同字符后,去往的都是同一个小组。最后,把同一个小组内的状态合并成一个状态。
- 例题 1 (NFA 确定化 —— 子集构造法):对如下含有空弧的 NFA 进行确定化(DFA化):
- 状态集:,初态为 ,终态(接受状态)为 。
- 转移弧关系:
- ,
- (空转换)
- 其 NFA 状态图如下:
stateDiagram-v2 [*] --> 0 0 --> 0 : a, b 0 --> 1 : a 1 --> 2 : ε 2 --> 3 : b classDef accept fill:#f9f,stroke:#333,stroke-width:2px; class 3 accept;
- 解答:
-
基础概念:
- -closure():从状态集 中的任何状态出发,仅通过空转换(-弧)所能到达的所有状态的集合(包含 本身)。
- :从状态集 中的任何状态出发,通过输入字符 所能到达的所有状态的集合。
-
逐步计算子集转移:
- 初态子集 : (由于 ,非接受状态)
- 计算 遇到 的转移:
- 得到新子集
- 计算 遇到 的转移:
- 计算子集 遇到 的转移:
- 计算子集 遇到 的转移:
- 得到新子集 (由于包含终态 ,为接受状态)
- 计算子集 遇到 的转移:
- 计算子集 遇到 的转移:
- 子集已经完全闭合,不再产生新子集。
-
整理得到 DFA 状态转移表:
DFA 状态 对应 NFA 状态子集 输入 输入 是否为接受状态 (初态) 否 否 (终态) 是 -
画出确定化后的 DFA 状态图:
stateDiagram-v2 [*] --> A A --> B : a A --> A : b B --> B : a B --> C : b C --> B : a C --> A : b classDef accept fill:#f9f,stroke:#333,stroke-width:2px; class C accept;
-
- 例题 2 (DFA 最小化 —— 分割法):将如下 DFA 最小化:
- 接受状态:;非接受状态:。
- 转移关系:状态 0 读 , 读 ;状态 1 读 , 读 ;状态 2 读 , 读 ;状态 3 读 , 读 ;状态 4 读 , 读 ;状态 5 读 , 读 。
- 解答:
- 第一步:初始划分:
- 接受状态组:
- 非接受状态组:
- 第二步:测试 是否需要拆分:
- 状态 0 和 1 读入 后跳到 (都在 内)。
- 状态 0 读入 跳到 2(在 内),状态 1 读入 跳到 4(在 内)。
- 它们的行为完全一样(都跳到同组状态),所以 不用拆分,记为状态 。
- 第三步:测试 是否需要拆分:
- 读入 时:
- 2 读
- 3 读
- 4 读
- 5 读
- 发现 2 和 4 跳去 ,而 3 和 5 跳去 。所以必须拆分为两组: 和 。
- 读入 时:
- 第四步:测试 是否需要拆分:
- 2 和 4 读 都跳去 。
- 2 读 ,4 读 。
- 行为一致,不用拆分,记为状态 。
- 第五步:测试 是否需要拆分:
- 3 和 5 读 都跳去 。
- 3 读 ,5 读 。
- 行为一致,不用拆分,记为状态 。
- 合并结论:化简后的 DFA 只有三个状态:、 和 。
- 第一步:初始划分:
模块三:自顶向下语法分析
题型八:消除左递归与提取左公因子
如何做:
- 消除自己推导自己开头的规则(直接左递归):
- 如果一条规则长得像:(大写字母 指向的串里,有的又以 开头,比如 ;有的不以 开头,比如 )。这会导致代换时无限循环。
- 我们引入一个带撇的新字母 ,把规则改成两行:
- 第一行把不以 开头的放在前面,后面强行加上新符号:。
- 第二行用新符号去接多出来的尾巴,并在最后加上空符号 :。
- 提取公因子(解决开头重合的问题):
- 如果大写字母有几个分支,开头都长得一模一样,例如 。这会导致我们在读到 时不知道该选哪条分支。
- 我们把相同的开头 提出来,后面接上一个新字母 :。
- 然后给新字母写一条规则,把后面不同的部分放进去:。
-
题目:消除规则 的左递归。
-
解答:
- 观察发现, 的规则没有左递归。
- 的规则中,第一个分支 是以 本身开头的(直接左递归)。其中, 部分是
, S,非左递归的分支 是 。 - 我们套用公式,引入新符号 :
- 第一步,写出不以 开头的分支,并接上 :
- 第二步,让 变出尾巴
, S并接上 自己,或者变为空:
- 消除左递归后的完整规则为:
-
例题 2:考虑文法 : 消去所有的左因子与左递归。
-
解答:
- 第一步:提取左公因子(消除回溯):
- 观察非终结符 的产生式, 具有公共左因子 。
- 我们把 提出来,引入新状态符号 ,将产生式改写为:
- 第二步:消除直接左递归:
- 观察非终结符 的产生式, 含有直接左递归。其中 是
, S, 是 。 - 套用消除左递归公式,引入新非终结符 ,改写为:
- 观察非终结符 的产生式, 含有直接左递归。其中 是
- 第三步:合并得到最终文法:
- 第一步:提取左公因子(消除回溯):
题型九:编写递归下降分析程序
如何做:
- 一个代号写一个函数:为规则中的每个大写字母写一个同名的处理函数/过程(比如
procedure S())。- 看字做选择:在过程内部,使用
if条件去判断当前读到的字符(用变量lookahead表示)。如果当前字符符合某条规则的开头,就进入那个分支。- 遇到符号就匹配,遇到代号就调用:
- 如果规则右边是具体符号(如小写字母、括号等),我们就写
match('该符号')。- 如果规则右边是大写字母(代号),我们就直接写这个大写字母的同名函数调用,例如
T()。- 匹配函数
match的作用:核对当前读到的字符是否就是我们期望的字符。如果是,就读入下一个字符;如果不是,就说明句子写错了,报错退出。
-
题目:对于产生式 ,写出递归下降分析程序。
-
解答:
// 匹配并读取下一个字符的辅助函数 procedure match(t: char); begin if lookahead = t then lookahead := nextToken() else error(); end; // 大写字母 S 的处理函数 procedure S(); begin if lookahead = 'a' then match('a') else if lookahead = '^' then match('^') else if lookahead = '(' then begin match('('); T(); match(')'); end else error(); end; // 大写字母 T 的处理函数 procedure T(); begin S(); Tprime(); end; // 大写字母 T' 的处理函数 procedure Tprime(); begin if lookahead = ',' then begin match(','); S(); Tprime(); end // 如果不是 ',',因为 T' 可以变为空(epsilon), // 只要当前字符是随后的合法字符(如右括号或结束符),就直接返回,什么都不用做 else if (lookahead = ')') or (lookahead = '$') then exit else error(); end; -
例题 2:已知文法如下,写出其递归下降分析程序:
-
解答: 为每个非终结符()分别编写分析过程,采用 Pascal/类 Pascal 伪代码实现:
// 辅助匹配过程 procedure match(t: char); begin if lookahead = t then lookahead := nextToken() else error(); end; procedure E(); begin T(); Eprime(); end; procedure Eprime(); begin if lookahead = '+' then begin match('+'); E(); end // 当面临 FIRST(E') 以外的符号时,若该符号属于 FOLLOW(E') = { ) , $ },可选择 epsilon 产生式归约并返回 else if (lookahead = ')') or (lookahead = '$') then exit else error(); end; procedure T(); begin F(); Tprime(); end; procedure Tprime(); begin // T' -> T | \varepsilon // FIRST(T) = { ( , a , b , ^ } if (lookahead = '(') or (lookahead = 'a') or (lookahead = 'b') or (lookahead = '^') then T() // FOLLOW(T') = FOLLOW(T) = { + , ) , $ } else if (lookahead = '+') or (lookahead = ')') or (lookahead = '$') then exit else error(); end; procedure F(); begin P(); Fprime(); end; procedure Fprime(); begin if lookahead = '*' then begin match('*'); Fprime(); end // FOLLOW(F') = FOLLOW(F) = { ( , a , b , ^ , + , ) , $ } else if (lookahead = '(') or (lookahead = 'a') or (lookahead = 'b') or (lookahead = '^') or (lookahead = '+') or (lookahead = ')') or (lookahead = '$') then exit else error(); end; procedure P(); begin if lookahead = '(' then begin match('('); E(); match(')'); end else if lookahead = 'a' then match('a') else if lookahead = 'b' then match('b') else if lookahead = '^' then match('^') else error(); end;
题型十:FIRST/FOLLOW集计算与预测分析表构造
如何做:
- FIRST集(首字符集合):对一个大写字母,看它顺着规则代换下去,第一个可能变出来的具体字符有哪些。
- 例如 ,那么 的首字符集合就是 。
- 如果 ,就要看 的首字符。如果 能变为空,还要把 的首字符也算进来。
- FOLLOW集(后跟字符集合):在所有规则的右边寻找某个大写字母(比如 ),看哪些具体字符有可能紧跟在 的后面出现。
- 初始的大写字母(如 )后面默认能放结束标记符 $$$。
- 如果有规则 ,那么 就在 的后面,把 加入 的后跟集。
- 如果有规则 (后面没东西了),或者后面的大写字母能变为空,那么 的后跟字符也都可以跟在 的后面,把 的后跟集并入 的后跟集。
- 填表规则:
- 表的行写大写字母,列写所有具体字符。
- 看一个大写字母的分支规则,它开头的具体字符有哪些,就把这条规则填到大写字母与那些字符对应的格子里。
- 如果该规则能变为空(),就把这条变为空的规则填到该大写字母的后跟字符(FOLLOW集里所有字符)对应的格子里。
-
题目:对以下规则,计算每个代号的 FIRST 和 FOLLOW 集合,并构造预测分析表:
-
解答:
- 计算 FIRST 集合:
- 只能由第一个分支产生
(或第二个分支产生a,所以: - 可以变成 (即有
(,a),或者变为空 ,所以: - 变成 ,以 开头,所以:
- 可以变成
,,或者变为空 ,所以:
- 只能由第一个分支产生
- 计算 FOLLOW 集合:
- 是起始代号,先把 \$$ 加入 \text{FOLLOW}(S)$。
- 观察所有规则右侧含 的地方:
- 在 中,右侧最末尾是 ,故将 放入 。
- 在 中,后面紧跟 ,故将 (即
,)放入 。因为 可变为空,所以要把 也放入。 - 在 中,同理。
- 综合计算后,各集合为:
- \text{FOLLOW}(S) = \{ \, ), , }$ (包括结束符、右括号、逗号)
- \text{FOLLOW}(S') = \text{FOLLOW}(S) = \{ \, ), , }$
- (在 中, 后面是右括号)
- 构造预测分析表:
代号 a ( ) , $
- 计算 FIRST 集合:
-
例题 2:考虑如下改写后的表达式文法:
- 计算非终结符的 与 。
- 构造 预测分析表。
- 给出输入句子
id - - id ( ( id ) )对应的详细移进与预测分析栈步骤表(以#作为栈底)。
-
解答:
-
计算 FIRST 和 FOLLOW 集合:
- \text{FOLLOW}(\textit{Expr}) = \{ \, ) }$
- \text{FOLLOW}(\textit{ExprTail}) = \text{FOLLOW}(\textit{Expr}) = \{ \, ) }$
- \text{FOLLOW}(\textit{Var}) = (\text{FIRST}(\textit{ExprTail}) \setminus \{\varepsilon\}) \cup \text{FOLLOW}(\textit{Expr}) = \{ -, \, ) }$
- \text{FOLLOW}(\textit{VarTail}) = \text{FOLLOW}(\textit{Var}) = \{ -, \, ) }$
-
构造预测分析表:
非终结符 id-()$ -
句子
id - - id ( ( id ) )的预测分析控制步骤表(使用分析栈与输入串配对,#为栈底):步骤 符号栈 (从右至左压栈) 剩余输入串 所用产生式 / 动作 1 #id - - id ( ( id ) ) #2 #id - - id ( ( id ) ) #3 #idid - - id ( ( id ) ) #匹配终结符 id(移进并消耗输入)4 #- - id ( ( id ) ) #(因 -)5 #- - id ( ( id ) ) #6 #-- - id ( ( id ) ) #匹配终结符 -(移进)7 #- id ( ( id ) ) #8 #-- id ( ( id ) ) #匹配终结符 -(移进)9 #id ( ( id ) ) #10 #id ( ( id ) ) #11 #idid ( ( id ) ) #匹配终结符 id(移进)12 #( ( id ) ) #(因 ()13 #)(( ( id ) ) #匹配终结符 ((移进)14 #)( id ) ) #15 #))(( id ) ) #匹配终结符 ((移进)16 #))id ) ) #17 #))id ) ) #18 #))idid ) ) #匹配终结符 id(移进)19 #))) ) #(因 ))20 #))) ) #(因 ))21 #))) ) #匹配终结符 )(移进)22 #)) #匹配终结符 )(移进)23 ##(因 #)24 ##分析成功结束,句子被接受
-
模块四:自底向上语法分析
题型十一:项目集规范族、DFA 与分析表构造 (LR 家族)
如何做:
- 初始规则拓广:在所有规则的最前面,强行加一条规则 (把初始大写字母用一个带撇的字母包起来),作为整套规则的新起点。
- 加圆点表示进度:在规则右侧的字符间塞入一个圆点
.,点在哪就表示我们读到了哪里。比如 表示我们刚刚读完了具体符号 ,接下来准备读代号 。- 求状态的扩展(闭包):
- 如果圆点后面紧跟着一个大写字母,比如 ,那就要把所有以 开头的规则全部写进这个状态里。
- 并且,写出来的这些新规则,它们的圆点必须放在最左边(例如 )。
- 状态转移:
- 看圆点后面紧贴的是什么符号(不管大写还是小写)。把圆点往右移过这个符号,得到一个新规则,作为新状态的起点,再重复第 3 步扩展它。
- 填分析驱动表:
- 如果圆点在最右边(如 ),说明这一支读完了,可以进行“归约”(即把读到的具体符号往回合并成代号 ),在表里填入
r加上规则编号。- 如果圆点后面是具体符号(如小写字母 ),且移位后去了状态 ,就在表里填
s j。- 如果圆点后面是大写字母(如 ),且移位后去了状态 ,就在跳转表(GOTO栏)里填数字
j。
-
题目:考虑规则 ,构造 LR(0) 项目集规范族及识别活前缀的 DFA,并构造 LR(0) 分析表。
-
解答:
- 编号规则:(1) , (2) , (3) 。
- 构造状态集(圆点移位与闭包扩展):
- 状态 0(初始): 先放起点的项目:。因为点后面是 ,所以引入以 开头的规则,点放最左:。 即状态 0 为:。
- 状态 1(状态 0 读 ):点往右移: 。 (这是接受状态 acc)
- 状态 2(状态 0 读 ):点往右移: 。因为点后面是大写字母 ,所以引入 开头的规则: , 。 即状态 2 为:。
- 状态 3(状态 2 读 ):点往右移: 。 (读完了,按第 (1) 条规则归约)
- 状态 4(状态 2 读 ):点往右移: 。点后面是 ,引入 的规则: , 。 即状态 4 为:。
- 状态 5(状态 2 读 ):点往右移: 。 (读完了,按第 (3) 条规则归约)
- 状态 6(状态 4 读 ):点往右移: 。 (读完了,按第 (2) 条规则归约)
- LR(0) 分析驱动表:
状态 a c d $ E A 0 s2 1 1 acc 2 s4 s5 3 3 r1 r1 r1 r1 4 s4 s5 6 5 r3 r3 r3 r3 6 r2 r2 r2 r2
-
题目 2 (SLR(1) 证明与表构造):证明文法 : 不是 文法而是 文法,并给出 分析表。
-
解答:
-
第一步:拓广文法并编号:
- (0)
- (1)
- (2)
- (3)
-
第二步:构造带圆点的项目状态集:
- 状态 0: (点后面是 ,引出 的规则) , (点后面是大写字母 和小写字母 ) (引入以 开头的规则) 即状态 0 为:
- 状态 1 (状态 0 读 ): (接受态)
- 状态 2 (状态 0 读 ):
- 状态 3 (状态 0 读 ): 点移位: 且 。由于点后面有大写字母 ,需引入以 开头的规则且点放最左:。 即状态 3 为:
- 状态 4 (状态 2 读 ): (按规则 1 归约)
- 状态 5 (状态 3 读 ):
- 状态 6 (状态 3 读 或状态 0 读 后归入) —— 点移过 : (按规则 3 归约)
- 状态 7 (状态 5 读 ): (按规则 2 归约)
-
第三步:指出 LR(0) 冲突并判断不是 LR(0):
- 观察状态 3,它同时包含了:
- 移进项目: (当读到具体字符 时,要把圆点右移)
- 归约项目: (读到了最右侧,要按规则 3 归约)
- 在同一个状态里,对于输入字符 ,既可以移进,又可以归约,这产生了移进-归约冲突。
- 结论:因为存在冲突,所以该文法不是 文法。
- 观察状态 3,它同时包含了:
-
第四步:利用 FOLLOW 集消解冲突,证明是 SLR(1):
- 我们求大写字母 后面可能紧跟的具体字符集合 :
- 在规则 中, 后面是具体符号 。
- 在规则 中, 后面是具体符号 。
- 所以,。
- 冲突发生时,移进的字符是 。由于 不在 集合中,说明当面临输入字符 时,我们绝对不可能把当前栈顶内容归约为 (因为如果归约成 ,后面就不可能合法地接上 )。
- 因此,在面临输入 时,我们只能选择移进;只有在面临输入 或 时,我们才选择归约。
- 结论:移进-归约冲突被成功解决,文法是 文法。
- 我们求大写字母 后面可能紧跟的具体字符集合 :
-
第五步:构造 SLR(1) 分析表:
- 对于归约项目 (状态 3 和状态 6),我们只在属于 的列中填入归约动作
r3。
SLR(1) 分析表:
状态 a b c $ S A 0 s3 1 2 1 acc 2 s4 3 s6 r3 r3 5 4 r1 5 s7 6 r3 r3 7 r2 - 对于归约项目 (状态 3 和状态 6),我们只在属于 的列中填入归约动作
-
-
例题 3 (SLR(1)/LR(1)/LALR(1) 冲突判定进阶): 考虑文法 :
- 列出该文法的所有 项目。
- 构造 项目集规范族及识别活前缀的 DFA。
- 判定该文法是否是 文法,若是,构造其预测分析表;若不是,说明理由。
- 该文法是 或 的吗?说明理由。
-
解答:
-
拓广文法并写出所有 LR(0) 项目: 拓广文法引入 。共 12 个项目: (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12)
-
构造项目集规范族与 DFA 转移关系:
DFA 状态转移表:
状态 接收 接收 接收 接收 - - - - - - - - -
判定是否是 SLR(1):
- 计算非终结符的 FOLLOW 集:
- \text{FOLLOW}(S) = \{ \, a, b }$
- 观察项目集 ,其中包含:
- 归约项目:(当面临输入属于 时归约)
- 移进项目:在 下,读入终结符 移进到 ,读入 移进到 。
- 因为 ,所以在面临输入字符 或 时,分析器既可以移进,又可以归约,存在移进-归约冲突。
- 结论:因为存在冲突,所以该文法不是 文法。
- 计算非终结符的 FOLLOW 集:
-
判定是否是 LALR(1) / LR(1):
- 结论:它既不是 文法,也不是 文法。
- 原因证明:该文法是一个二义性文法。对于同一个句子(例如
abab),可以构造出两棵不同的语法树:- 树 1:
- 树 2: 由于二义性文法在 LR 类分析器中必然存在无法消解的移进-归约或归约-归约冲突,因此该文法绝不可能是 或 文法。
-
-
例题 4 (LR(1) 与 LALR(1) 状态合并机制讲解): 考虑经典的非 SLR(1) 文法 : 解释为何 SLR(1) 会产生冲突,以及 LR(1) 和 LALR(1) 如何利用向前看搜索符消解该冲突。
-
解答:
- SLR(1) 冲突原因:
- 计算其 集有 \text{FOLLOW}(R) = \{ \, = }$。
- 在 LR(0) 状态集构建中,会产生一个状态 。
- 当输入为
=时,在 下:- 根据移进项目 ,应当移进。
- 根据归约项目 ,因为 ,在 SLR(1) 规则下应当归约。
- 这在
=处产生了移进-归约冲突,故文法不是 。
- LR(1) 的消解机制:
- LR(1) 项目的形式为 ,其中 是向前看搜索符(由逆向分析 集而来)。
- 在 LR(1) 中,初始状态 为:
- [S' \to \cdot S, \]$
- [S \to \cdot L = R, \]$
- [S \to \cdot R, \]$
- [L \to \cdot *R, = / \]L=R$$)
- [L \to \cdot \text{id}, = / \]$
- [R \to \cdot L, \]R$$)
- 当从 读入 转移到 时,得到项目集:
- [S \to L \cdot = R, \]$
- [R \to L \cdot, \]$
- 消解效果:观察归约项目 [R \to L \cdot, \]$$**,而不包含
=。 - 此时,若面临输入
=,分析器只能选择移进(因为移进=属于项目 [S \to L \cdot = R, \]$$,才选择归约。移进-归约冲突被成功消解!
- LALR(1) 的合并同心集:
- LR(1) 状态机往往有几百个状态,有很多状态的 LR(0) 核心相同,只有搜索符不同(同心集)。
- LALR(1) 将这些同心状态进行合并,例如将 [R \to L \cdot, \][R \to L \cdot, =][R \to L \cdot, = / $]$。
- 合并搜索符后,LALR(1) 状态数与 LR(0) 一致,在此文法中合并后依然没有冲突,成功证明该文法是 LALR(1) 的。
- SLR(1) 冲突原因:
题型十二:移进-归约过程追踪
如何做:
- 准备四列:状态栈(里面只存状态数字,刚开始是
0)、符号栈(存读到的符号,刚开始是$)、输入串(待处理的句子,末尾加$)、动作。- 查表走步:
- 看状态栈最顶上的那个数字(例如当前栈顶是
2),和输入串最左边的第一个字符(例如当前是c)。- 去刚才做好的分析表里查对应的格子(交叉点
[2, c])。- 如果格子里写着
s4:
- 把当前字符
c从输入串里删掉,塞入符号栈。- 把数字
4塞入状态栈。- 动作记为“移进”。
- 如果格子里写着
r3(说明要用第 3 条规则归约,假设第 3 条规则是 ):
- 规则右边有几个符号(这里只有 1 个符号 ),就从符号栈和状态栈的顶部各弹出几个元素(这里各弹出 1 个)。
- 把规则左边的大写字母(这里是 )塞入符号栈。
- 此时看一眼状态栈顶剩下的数字(假设是
2),查分析表里的 GOTO 栏(格子[2, A],假设填的是3)。把这个数字3塞入状态栈。- 动作记为“用规则 3 归约”。
- 重复执行,直到动作出现
acc(接受),说明句子完全符合规则。
-
题目 1:利用上题构造的分析表,给出句子
accd的移进-归约分析详细步骤。 -
解答:
步骤 状态栈 符号栈 输入串 动作 1 0 $ accd$ 读入首字符 a,查表[0, a]得到s2,移进2 0 2 $a ccd$ 读入 c,查表[2, c]得到s4,移进3 0 2 4 $ac cd$ 读入 c,查表[4, c]得到s4,移进4 0 2 4 4 $acc d$ 读入 d,查表[4, d]得到s5,移进5 0 2 4 4 5 $accd $ 查表 [5, $]得到r3(第3条规则是 )。右边长为1,两栈各弹1个元素。符号栈压 ,查[4, A]得到 6,状态栈压 6。用 归约6 0 2 4 4 6 $accA $ 查表 [6, $]得到r2(第2条规则是 )。右边长为2,两栈各弹2个元素。符号栈压 ,查[4, A]得到 6,状态栈压 6。用 归约7 0 2 4 6 $acA $ 查表 [6, $]得到r2(第2条规则是 )。右边长为2,两栈各弹2个元素。符号栈压 ,查[2, A]得到 3,状态栈压 3。用 归约8 0 2 3 $aA $ 查表 [3, $]得到r1(第1条规则是 )。右边长为2,两栈各弹2个元素。符号栈压 ,查[0, E]得到 1,状态栈压 1。用 归约9 0 1 $E $ 查表 [1, $]得到acc。分析成功 -
例题 2:对于文法 ,给出句子 的规范归约过程(最右推导的逆过程,每一步写出当前句型及归约句柄)。
-
解答: 规范归约就是每次找到句型中最左侧的直接短语(即句柄),查规则将其归约为左边的非终结符。 详细步骤如下:
- 当前句型为:
(((a, a), ∧, (a)), a)。最左侧的直接短语是第一个a,使用 归约。- 句型:
(((S, a), ∧, (a)), a),句柄为:a
- 句型:
- 接下来最左侧直接短语是第二个
a,使用 归约。- 句型:
(((S, S), ∧, (a)), a),句柄为:a
- 句型:
- 接下来最左侧直接短语是第一个
S,使用 归约。- 句型:
(((T, S), ∧, (a)), a),句柄为:S
- 句型:
- 接着
T, S一步归约为 。- 句型:
(((T), ∧, (a)), a),句柄为:T, S
- 句型:
- 接着最左直接短语是
(T),使用 归约。- 句型:
((S, ∧, (a)), a),句柄为:(T)
- 句型:
- 接着最左直接短语是第一个
S,使用 归约。- 句型:
((T, ∧, (a)), a),句柄为:S
- 句型:
- 接着最左直接短语是
∧,使用 归约。- 句型:
((T, S, (a)), a),句柄为:∧
- 句型:
- 接着
T, S一步归约为 。- 句型:
((T, (a)), a),句柄为:T, S
- 句型:
- 接着最左直接短语是括号里的
a,使用 归约。- 句型:
((T, (S)), a),句柄为:a
- 句型:
- 接着最左直接短语是
S,使用 归约。- 句型:
((T, (T)), a),句柄为:S
- 句型:
- 接着最左直接短语是
(T),使用 归约。- 句型:
((T, S), a),句柄为:(T)
- 句型:
- 接着
T, S一步归约为 。- 句型:
((T), a),句柄为:T, S
- 句型:
- 接着最左直接短语是
(T),使用 归约。- 句型:
(S, a),句柄为:(T)
- 句型:
- 接着最左直接短语是
S,使用 归约。- 句型:
(T, a),句柄为:S
- 句型:
- 接着最左直接短语是最后一个
a,使用 归约。- 句型:
(T, S),句柄为:a
- 句型:
- 接着
T, S一步归约为 。- 句型:
(T),句柄为:T, S
- 句型:
- 最终
(T)一步归约为开始符号 。- 句型:
S,句柄为:(T) - 归约成功!
- 句型:
- 当前句型为:
模块五:语义分析、语法制导翻译与中间代码
题型十三:语法制导定义(SDD)与属性计算
如何做:
- 理解属性:属性就是挂在代号(大写字母)身上的值,比如计算出来的长度、结果数值等。
- 综合属性(自底向上计算):如果一个代号的属性值,是直接根据它底下的分支子节点的属性值算出来的。
- 计算顺序:先算出底层叶子的值,再一层层往上加,最后算出树顶(根部)的值。
- 属性计算步骤:
- 画出句子的代换树。
- 从树的最底层开始,按照给定的数学公式算出每一个节点属性的值。
- 顺着树枝往上代入,直到算出最顶上那个字母的属性。
-
题目:代换规则如下,其语义规则定义了长度计算 。求句子
a bbcc b(对应结构为 , , )的属性计算步骤。 -
解答:
- 画出该句子的代换树结构:
- 节点分支出 , , 。
- 节点分支出 , , 。
- 节点分支出 。
- 根据语义规则,自底向上代入计算:
- 最底层 :其属性 。
- 中间层 :根据公式 ,代入得 。
- 最顶层 :根据公式 ,代入得 。
- 最终计算结果为 。
- 画出该句子的代换树结构:
-
例题 2 (S-属性文法设计与属性计算): 设计一个全综合属性的 S-属性文法,能够自底向上计算二进制小数(例如
101.101)对应的十进制数值,给出产生式和对应的语义规则,并写出计算101.101值的详细步骤。 -
解答:
-
设计属性:
- 为单个二进制位 引入属性 (对应值 0 或 1)。
- 为二进制串 引入属性 (对应部分计算出的十进制数值)以及 (对应的二进制位串长度,用于计算小数时的权重移动)。
- 为开始符号 引入属性 (最终十进制数值)。
-
构造 S-属性语法制导定义 (SDD):
产生式 语义规则 -
计算
101.101的具体步骤(自底向上):- 整数部分 :
- 最左边位 。
- 归约为单字符串 。
- 第二位 。
- 归约为两位串 。
- 第三位 。
- 归约为三位串 。
- 小数部分 (运算规则与整数部分相同):
- 经过同样的推导,计算出 。
- 整句归约 :
- 根据定义:。
- 代入数据:。
- 最终十进制结果为 。
- 整数部分 :
-
题型十四:逆波兰式(后缀式)与三地址代码转换
如何做:
- 逆波兰式(后缀表达式):
- 平时我们写的算式是“运算符在中间”,如 。逆波兰式要求把“运算符移到后面”,变成 。
- 遇到括号时,先算括号里面的。括号本身在最后的结果里不写出来。
- 复杂的式子可以先画成一棵运算树(运算符在交叉点上,数字在树叶上),然后按照“左分叉、右分叉、最后运算符”的顺序把它们写下来。
- 三地址四元式:
- 把复杂的长算式拆成一步步只包含两个运算数的简短式子,每一步算完的值存入一个临时变量(如 , )。
- 每一行写成四个位置的格式:
(操作符, 第一个运算数, 第二个运算数, 结果临时变量)。如果是一元运算(如单目减法 ),没有第二个运算数,就在第三个位置写下划线_。
-
题目 1:给出赋值语句 生成的四元式序列。
-
解答:
- 观察优先级,最先计算括号内的单目减法 。写成四元式,存入临时变量 :
(-, C, _, T1)(注意:单目减没有第二个操作数,用下划线占位) - 接着计算 ,存入 :
(+, T1, D, T2) - 接着计算括号外面的乘法,即 ,存入 :
(*, B, T2, T3) - 最后把乘出来的结果 赋值给变量 :
(:=, T3, _, A)
- 观察优先级,最先计算括号内的单目减法 。写成四元式,存入临时变量 :
-
例题 2 (算术/布尔表达式后缀式转换): 写出以下两个表达式的逆波兰(后缀)表示形式:
- (特别注意单目减号处理)
-
解答:
- 对于 :
- 单目减号
-在这里只对 起作用,可以用特殊的单目负操作符uminus(或@)表示。 - 运算树的根节点是
*,其左子树为a,右子树为括号内的( -b + c )(根节点为+,左子树为-b,右子树为c)。 - 后缀式按照“左儿子、右儿子、根”的深度遍历:
- 首先遍历
a。 - 然后遍历右侧,先写
b和单目负uminus得到b uminus;再写右子树c;最后写运算+。得到b uminus c +。 - 最后输出根操作符
*。
- 首先遍历
- 最终逆波兰式: (注:考试中若不特别要求单目负表示,也可简化写为:)
- 单目减号
- 对于 :
- 根据算符优先级,单目操作符
not的优先级高于双目操作符or。 - 逐步转换后缀式:
- 第一个项 转换为:
A not。 - 括号内的 转换为:
C D not or。 - 括号外的 作用于其上,转换为:
C D not or not。 - 最后将前半截和后半截使用根部的
or连接。
- 第一个项 转换为:
- 最终逆波兰式:
- 根据算符优先级,单目操作符
- 对于 :
题型十五:控制流语句的四元式序列生成
如何做:
- 短路计算:
- :如果前面的条件 成立,就不用看后面的条件 了,直接跳去真目标。
- :如果前面的条件 不成立,直接判定为假,不用看 ,跳去假目标。
- 跳转四元式的写法:
- 条件跳转:比如“若 则跳到第 103 行”,写成:
(j<, a, b, 103)。如果不满足,就直接顺着执行下一行。- 无条件跳转:直接跳到某一行,写成:
(j, _, _, 106)。- 行号规划:
- 假设从指定的行号(如 100)开始写,每写一行行号加 1。
- 遇到需要跳转的目标行号,如果还没写到那一行,可以先空着,等写到目标行后再把行号倒回去填上(回填)。
-
题目 1:把程序段
while a < b do if c < d then x := y + z翻译成四元式代码,假设起始行号为 100,采用短路计算。 -
解答:
- 100行:这是
while循环判定条件的开始。我们先测试条件 ,如果满足,就跳进循环体内(假设跳到 102 行开始判断if条件);如果不满足,循环结束,直接跳出循环(跳到最后一行的下一行,假设是 107 行):100: (j<, a, b, 102)101: (j, _, _, 107) - 102行:开始判断
if c < d条件。如果满足,执行then后面的赋值(假设是 104 行);如果不满足,if结束,无操作,继续下一次循环(回到 100 行):102: (j<, c, d, 104)103: (j, _, _, 100) - 104行:执行加法赋值
x := y + z。算完之后,因为是循环体,必须无条件跳回最开始的 100 行进行下一次判定:104: (+, y, z, T1)105: (:=, T1, _, x)106: (j, _, _, 100) - 检查发现,循环结束的出口行应该在 106 行之后,即 107 行。回填 101 行的假跳转目标为 107:
- 完整四元式序列:
100: (j<, a, b, 102) 101: (j, _, _, 107) 102: (j<, c, d, 104) 103: (j, _, _, 100) 104: (+, y, z, T1) 105: (:=, T1, _, x) 106: (j, _, _, 100) 107: (nop)
- 完整四元式序列:
- 100行:这是
-
例题 2 (复杂嵌套控制流与短路计算四元式生成): 将如下带有嵌套 while 循环和
and逻辑运算的控制流语句翻译为四元式序列。假设起始行号为 100,布尔表达式采用短路计算:while A < C and B < D do if A = 1 then C := C + 1 else while A <= D do A := A + 2; -
解答:
-
设计分析逻辑(带回填机制):
- 行 100:开始评估外层
while的第一个条件A < C。如果为真,跳转到 102 行评估第二个条件;如果为假,说明条件短路失败,跳出整个外层循环(跳转目标待定为出口行 114)。 - 行 102:评估外层
while的第二个条件B < D。如果为真,说明整个and条件成立,跳入循环体(即 104 行的if判断);如果为假,退出循环(跳到 114 行)。 - 行 104:外层循环体内,评估
if A = 1。若为真,跳转到then分支的计算(106 行);若为假,跳转到else分支(即内层while循环起始的 109 行)。 - 行 106-107:
then分支,计算C := C + 1。计算完后,代表外层循环的一轮体结束,必须无条件跳转回 100 行。 - 行 109:
else分支,开始评估内层while条件A <= D。如果为真,跳入内层循环体(111 行);如果为假,退出内层循环,因为外层循环体在此也结束了,所以直接跳回 100 行。 - 行 111-112:内层循环体内,计算
A := A + 2。计算完后,无条件跳转回内层条件评估点(109 行)。 - 行 114:外层循环出口。
- 行 100:开始评估外层
-
生成完整四元式序列:
序号 操作符 运算数 1 运算数 2 结果 (跳转目标 / 临时变量) 说明 100 j<判断条件 (真则看下一个) 101 j__条件不满足,退出外层循环 102 j<判断条件 (真则入循环体) 103 j__条件不满足,退出外层循环 104 j=外层循环体首:判断 (真则去 then) 105 j__假则去 else 分支(内层循环) 106 +then 分支: 107 :=_赋值给 108 j__结束后跳转回外层循环开头 109 j<=else/内层循环开始:判断 110 j__内层循环结束,返回外层循环开头 111 +内层循环体: 112 :=_赋值给 113 j__结束后跳转回内层循环开头 114 nop___外层循环退出点
-
模块六:代码优化
题型十六:基本块划分与控制流图 (CFG) 构造
如何做:
- 基本块(一气呵成执行的代码段):
- 基本块的特点是“一旦从入口进去,就必须一气呵成地执行到出口,中间不能跳走,也不能从别的路插进来”。
- 第一步,找到所有块的入口语句(也叫 Leader):
- 规则一:第一行语句是一个入口。
- 规则二:任何跳转语句(有条件或无条件跳转)的目标语句是一个入口。
- 规则三:紧跟在跳转语句后面的那一行语句是一个入口。
- 第二步,切分基本块:
- 从每一个入口语句开始往下数,一直到下一个入口语句的前一行,切开,这就是一个独立的基本块。
- 第三步,画连接图 (CFG):
- 把每一个块画成一个方框。
- 画带箭头的连线:如果执行完块 1,有可能顺着走到块 2,或者通过块 1 末尾的跳转跳到块 2,就画一条从块 1 指向块 2 的箭头。
- 题目:对下面的程序,划分基本块,并画出其控制流图:
(1) A := 0 (2) B := 1 (3) L1: A := A + B (4) if B >= C goto L2 (5) B := B + 1 (6) goto L1 (7) L2: write A (8) halt - 解答:
- 寻找入口语句 (Leader):
- 根据规则一:语句 (1) 是第一行,故 (1) 是入口。
- 根据规则二:
- 语句 (4) 中的跳转目标是
L2,即语句 (7),故 (7) 是入口。 - 语句 (6) 中的跳转目标是
L1,即语句 (3),故 (3) 是入口。
- 语句 (4) 中的跳转目标是
- 根据规则三:
- 语句 (4) 是条件跳转,紧跟其后的是语句 (5),故 (5) 是入口。
- 语句 (6) 是跳转,紧跟其后的是语句 (7)(已是入口)。
- 所有的入口语句为:(1), (3), (5), (7)。
- 切分基本块:
- 基本块 B1(从入口 (1) 到下一个入口 (3) 之前):包含 (1), (2)。
- 基本块 B2(从入口 (3) 到下一个入口 (5) 之前):包含 (3), (4)。
- 基本块 B3(从入口 (5) 到下一个入口 (7) 之前):包含 (5), (6)。
- 基本块 B4(从入口 (7) 到结尾):包含 (7), (8)。
- 画控制流图 (CFG):
- 块 B1 执行完后,直接顺着走到块 B2。连线:
B1 -> B2。 - 块 B2 结尾的条件跳转,若满足跳去
L2(即块 B4);若不满足,直接顺着走到块 B3。连线:B2 -> B3,B2 -> B4。 - 块 B3 结尾无条件跳转去
L1(即块 B2)。连线:B3 -> B2。 - 连成的控制流图为:
graph TD B1["基本块 B1<br>(1)-(2)"] --> B2["基本块 B2<br>(3)-(4)"] B2 -->|不满足跳转| B3["基本块 B3<br>(5)-(6)"] B2 -->|满足跳转| B4["基本块 B4<br>(7)-(8)"] B3 --> B2
- 块 B1 执行完后,直接顺着走到块 B2。连线:
- 寻找入口语句 (Leader):













