banner
NEWS LETTER

5. 语义分析

Scroll down

1. Motivation (动机:为什么需要语义分析?)

语法分析(Syntax Analysis)只能处理上下文无关(Context-Free)的结构,无法发现与上下文有关(Context-Dependent)的错误。单纯的语法正确并不代表程序有意义。
我们需要语义分析来解决以下具体问题:

  • 标识符作用域 (Scope):变量是否在引用的位置可见?(例如:在块外引用块内定义的变量)。
  • 重复声明:同一作用域内是否存在同名变量?
  • 类型一致性 (Type Consistency)
    • 操作数类型是否匹配?(例如:boolean 类型参与算术加法)。
    • 赋值号两边类型是否兼容?
    • 数组下标是否为整数?
    • 函数调用的实参和形参是否匹配?
  • 控制流合法性:break 是否出现在循环语句中?

解决方案:利用语法制导翻译技术,在语法分析的过程中嵌入语义动作,收集信息(符号表)并进行检查。

2. Concept (核心概念)

2.1 语义分析的任务

  1. 收集信息:将标识符的属性(类型、存储位置、作用域等)存入符号表。
  2. 类型检查:验证语法成分是否满足语义规则。

2.2 符号表 (Symbol Table)

  • 定义:一种记录标识符属性(名字、类型、地址、维数、声明行、引用行、指针等)的数据结构。
  • 动态性:在编译过程中,符号表的入口不断增加(进入新作用域),有时也不断删除(退出作用域)。

2.3 类型表达式 (Type Expressions)

类型本身也有结构,递归定义如下:
* 基本类型:boolean, char, integer, real, type_error, void。
* 类型名:即类型别名。
* 构造类型(由类型构造器生成):
* 数组 (Array),其中 是下标范围, 是元素类型。
* 笛卡儿积/记录 (Record)(各域类型的组合)。多用于结构体
* 指针 (Pointer),指向类型 的对象。
* 函数 (Function),从定义域类型 映射到值域类型

2.4 类型等价 (Type Equivalence)

判断两个类型 是否相同,有两种标准:
1. 结构等价 (Structural Equivalence)
* 是相同的基本类型。
* 或者是将同一类型构造器应用于结构等价的内部类型上。
* 注意:C语言数组作为参数传递时,通常不检查第一维界限,这属于结构等价的特例调整。
2. 名字等价 (Name Equivalence)
* 两个类型表达式等价,当且仅当它们是同一个类型名。
* Pascal 和 C (对于 struct/union/enum) 通常采用名字等价。

3. Method (实现方法与算法)

3.1 符号表的组织与操作

针对块结构语言(如 Pascal, C),支持嵌套作用域。

A. 组织形式
1. 栈式符号表
* 新作用域的变量压入栈顶。
* 退出作用域时,栈顶相关的变量出栈。
* 优点:自然实现“最近嵌套作用域原则”。
2. 栈式哈希表
* 结合哈希表提高查找效率。
* 哈希表存头指针,具体记录通过链表连接(链表方向指向同一哈希桶的旧记录)。
* 同时维护一个 scope 栈来管理作用域的进入和退出。

B. 核心操作
* Insert (插入):在当前作用域(栈顶子表)添加新名字。需检查重复声明。
* Lookup (检索):从当前作用域(栈顶)向外层(栈底)线性查找。
* Locate (定位):进入新块时,建立新子表(或标记栈位置)。
* Relocate (重定位):退出块时,删除该块的子表,恢复外层环境。

3.2 结构等价判定算法 seqtest(s, t)

递归判断两个类型表达式是否结构等价:
* 若 为相同基本类型 return true。
* 若 递归检查 seqtest(s1, t1) && seqtest(s2, t2)。
* 若 递归检查 seqtest(s1, t1)。
* 若 (函数) 且 递归检查参数和返回值。
* 否则 return false。

3.3 简单类型检查程序 (语法制导翻译 SDT)

通过在产生式中嵌入语义动作实现。

A. 变量声明的处理
* 利用全局变量 offset 记录相对地址。
* 基本类型:设置 T.type 和 T.width(如 integer 宽4,real 宽8)。
* 数组
* 声明动作:调用 enter(id.name, T.type, offset) 填表,并更新 offset = offset + T.width。

B. 过程与作用域的处理
* 使用 符号表栈 (tableptr)偏移量栈 (offset)
* 进入过程 (P -> M D; S)
* M -> ε: 创建新表 maketable(top(tableptr)),将其压入 tableptr 栈;压入新 offset=0。
* 退出过程
* 计算总宽度,弹出 tableptr 和 offset,恢复外层环境。
* 将该过程及其符号表挂载到外层符号表中 (enterproc)。

C. 表达式类型检查
综合属性 E.type 用于向上传递类型。
* 算术运算 ()
* 若 均为 integer integer。
* 若其中一个是 real real(发生隐式转换)。
* 否则 type_error。
* 函数调用 ()
* 查找 id,检查 p 是否非空。
* 检查类型是否为函数 (fun)。
* 检查参数个数 (rparam.num) 和参数类型 (checkptype) 是否匹配。
* 结果类型设为返回值类型。

D. 语句类型检查
* 赋值 ()
* 查找 id。
* 检查 id.type 是否等于 E.type。
* 控制流 ()
* 必须检查 E.type == boolean。

4. Example (经典例题与场景)

例 1:结构等价 vs 名字等价 (C语言结构体)

struct { int i; char c; } x, y;  
struct { int i; char c; } b;  
  • 结构等价:x, y, b 的内部结构完全一样(int + char),三者等价。

  • 名字等价(C语言采用):

    • x 和 y 在同一条语句声明,视为使用同一个内部生成的类型名,相互等价。

    • b 是另一次结构体定义,编译器生成了新的内部名字,因此 x 与 b 不等价。

例 2:SDT 应用(声明语句翻译)

输入:i: integer; x: real; A: array[10] of char;

过程推导:

  1. 初始 offset = 0。

  2. i: integer enter(i, integer, 0) offset 变为 4。

  3. x: real enter(x, real, 4) offset 变为 4+8=12。

  4. A: array[10] of char 宽度 enter(A, array(10, char), 12) offset 变为 22。

例 3:符号表逻辑结构 (Pascal Sort 程序)

场景:主程序 sort 包含子过程 quicksort,quicksort 包含子过程 partition。

查找链条:

  • Head Area (橙色箭头):建立作用域链。

    • partition 表头指向 quicksort 表头。

    • quicksort 表头指向 sort 表头。

    • sort 表头指向 nil。

  • 查找 x 的过程:如果在 partition 中引用 x(假设它是 sort 中定义的全局变量),编译器会沿着橙色箭头:partition (无) quicksort (无) sort (找到 x)。

如果您喜欢我的文章,可以考虑打赏以支持我继续创作.

其他文章
目录导航 置顶
  1. 1. 1. Motivation (动机:为什么需要语义分析?)
  2. 2. 2. Concept (核心概念)
    1. 2.1. 2.1 语义分析的任务
    2. 2.2. 2.2 符号表 (Symbol Table)
    3. 2.3. 2.3 类型表达式 (Type Expressions)
    4. 2.4. 2.4 类型等价 (Type Equivalence)
  3. 3. 3. Method (实现方法与算法)
    1. 3.1. 3.1 符号表的组织与操作
    2. 3.2. 3.2 结构等价判定算法 seqtest(s, t)
    3. 3.3. 3.3 简单类型检查程序 (语法制导翻译 SDT)
  4. 4. 4. Example (经典例题与场景)
    1. 4.1. 例 1:结构等价 vs 名字等价 (C语言结构体)
    2. 4.2. 例 2:SDT 应用(声明语句翻译)
    3. 4.3. 例 3:符号表逻辑结构 (Pascal Sort 程序)
请输入关键词进行搜索