综合属性和继承属性

综合属性 (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
2
3
4
   E
/|\
/ | \
E1 + T

语义规则(综合属性):

  1. E.val = E1.val + T.val

    • 含义: 父亲 E 的值,是通过综合其孩子 E1 的值和 T 的值(相加)得到的。
    • E1T 必须先计算出自己的 val,然后 E 才能计算。信息从 E1T 流向 E
  2. 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

问题: 在语法树中,ab 这两个节点(在 L 的子树中)怎么知道自己的类型是 int 呢? int 这个信息在它们的兄弟节点 T 那里。

语法树节点:

1
2
3
4
5
6
7
      D
/ \
/ \
T L
(int) / \
/ \
a b

语义规则(继承属性):

  1. L.in_type = T.type (in_type 是 “inherited type” 的缩写)

    • 含义: 这是一个横向传递。在父节点 D 的层面,它把孩子 T 的属性(T.type,比如 “int”)拿过来,传递(赋值)给另一个孩子 L 的属性(L.in_type)。
  2. L 的子节点(比如 ab)会继续继承 L.in_type

    • L 展开时(例如 L -> id, L1L -> id),id 节点就可以从 L 那里拿到 in_type
    • id.type = L.in_type
    • 含义: 孩子 id(例如 a)的类型,是继承自其父亲 Lin_type 属性。

总结: 继承属性非常适合**“自顶向下”**传递“上下文信息”(Context)。int 就是 ab 的上下文。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) —— 这个函数参数,就是继承属性的最直观实现!