综合属性 (Synthesized Attributes) 和 继承属性 (Inherited Attributes) 是语法制导翻译 (Syntax-Directed Translation, SDT) 的两个核心概念。
简单来说,它们是附着在语法树节点上的“信息”或“值”,是编译器用来实现语义分析(就是您第 6 章的那些内容,如类型检查、生成中间代码)的具体技术手段。
💡 核心思想:信息如何在语法树上传递?
-
综合属性:信息自底向上传递。
子节点(孩子)计算出自己的值,然后父节点(父亲)综合这些值来计算出自己的值。
-
继承属性:信息自顶向下传递。
父节点(父亲)将自己的某个值,传递给子节点(孩子),让孩子继承这个值来辅助计算。
1. 综合属性 (Synthesized Attributes)
定义: 语法树中某个节点(非终结符)的属性值,是由它的子节点的属性值计算得到的。
信息流向: 自底向上 (Bottom-up)。
经典例子: 表达式求值 或 表达式翻译(对应您的 6.4 节)。
假设我们有文法 E -> E1 + T (表达式 E 由 表达式 E1 加上 项 T 构成)。
我们要计算表达式的值(val)或翻译成三地址码(code)。
语法树节点:
1 | E |
语义规则(综合属性):
-
E.val = E1.val + T.val- 含义: 父亲
E的值,是通过综合其孩子E1的值和T的值(相加)得到的。 E1和T必须先计算出自己的val,然后E才能计算。信息从E1和T流向E。
- 含义: 父亲
-
E.code = E1.code + T.code + "ADD"(简化的三地址码)- 含义: 父亲
E的中间代码,是通过综合(拼接)其孩子E1的代码、T的代码,再加上一条 “ADD” 指令得到的。
- 含义: 父亲
总结: 综合属性非常适合**“自底向上”**的任务。你必须先知道子表达式的值,才能计算整个表达式的值。S-属性定义 (S-Attributed Definition) 是一个只使用综合属性的 SDT。
2. 继承属性 (Inherited Attributes)
定义: 语法树中某个节点的属性值,是由它的父节点或兄弟节点的属性值计算得到的。
信息流向: 自顶向下 (Top-down) 或 横向 (Sideways)。
经典例子: 变量声明翻译(对应您的 6.2 节)。
假设我们有文法 D -> T L (声明 D 由 类型 T 和 变量列表 L 构成),例如 int a, b;。
-
T对应int -
L对应a, b
问题: 在语法树中,a 和 b 这两个节点(在 L 的子树中)怎么知道自己的类型是 int 呢? int 这个信息在它们的兄弟节点 T 那里。
语法树节点:
1 | D |
语义规则(继承属性):
-
L.in_type = T.type(in_type 是 “inherited type” 的缩写)- 含义: 这是一个横向传递。在父节点
D的层面,它把孩子T的属性(T.type,比如 “int”)拿过来,传递(赋值)给另一个孩子L的属性(L.in_type)。
- 含义: 这是一个横向传递。在父节点
-
L的子节点(比如a和b)会继续继承L.in_type- 当
L展开时(例如L -> id, L1或L -> id),id节点就可以从L那里拿到in_type。 id.type = L.in_type- 含义: 孩子
id(例如a)的类型,是继承自其父亲L的in_type属性。
- 当
总结: 继承属性非常适合**“自顶向下”**传递“上下文信息”(Context)。int 就是 a 和 b 的上下文。L-属性定义 (L-Attributed Definition) 是可以使用综合属性和特定继承属性的 SDT。
总结对比
| 特性 | 综合属性 (Synthesized) | 继承属性 (Inherited) |
|---|---|---|
| 计算依赖 | 仅依赖子节点 | 依赖父节点或兄弟节点 |
| 信息流向 | 自底向上 (Bottom-up) | 自顶向下 (Top-down) 或 横向 |
| 经典用例 | 表达式求值 (6.4 节) | 变量声明 (6.2 节) |
| 形象比喻 | 孩子们向父母汇报工作,父母汇总 | 父母给孩子们下达指令或传递信息 |
| 分析器关联 | 易于在自底向上分析器 (如 YACC) 中实现 | 易于在自顶向下分析器 (如 递归下降法) 中实现 |
它们如何与您的补充知识点关联?
-
递归下降法(自顶向下分析)天然适合实现L-属性定义(即包含继承属性的定义)。
-
为什么? 当你(在
parse_D函数中)刚解析完T(调用parse_T),你立刻就得到了T.type。然后你马上去调用parse_L,此时你就可以把T.type作为一个参数传递给parse_L函数。 -
parse_L(T.type)—— 这个函数参数,就是继承属性的最直观实现!