Skip to content

算法Algorithm

Steensgaard 合一式指针分析

Steensgaard pointer analysis · Unification-based points-to analysis

把可能别名合成等价类为什么更快,又会在哪个小程序上丢掉精度。

形式陈述 ​

Steensgaard分析用存储形状的等价类近似可能地址,允许把多个目标合成一个代表,以获得近线性的求解成本。它与Andersen包含分析一样通常是流不敏感、上下文不敏感;差别在于目标关系的表示与合并方式。

本页选原算法的无函数值、无字段指针片段。每个变量位置x有位置代表 Lx,其形状为 ref(Tx);Tx 是该单元可能保存的指针所指向的位置类。各代表通过并查集管理,尚无已知形状时记为 ⊥。多个实际变量位置的标签可以落在同一等价类里。

注意 Lx 与 Tx 不是一回事:前者代表变量x自身所在的地址,后者代表它存的指针指向哪里。语句 x=y传播的是存储内容的目标形状,不是断言变量x和y位于同一个地址。具体含义仍以可变存储为基准。

合并与条件合并 ​

join(a,b)合并两个代表;若两者形状分别为 ref(a′) 和 ref(b′),还要递归合并子代表 a′,b′。若一边尚为 ⊥,采用另一边形状;当某类从 ⊥ 变成有形状时,唤醒先前登记的条件合并。

cjoin(dst,src)遵循输入到输出方向:若src已有非底形状,就执行join;若src仍为 ⊥,先把dst登记到src的待办集合,等src出现形状后再join。原论文用这种延迟避免源端没有指针信息时过早把两者绑在一起。简单教学版本常用无条件合一替代它,那会更粗,不能声称结果与原版完全一致。

对规范化指针语句,核心规则为:

  • x=&y:join(Tx,Ly)
  • x=y:cjoin(Tx,Ty)
  • x=*y:若 Ty 尚无形状,赋予 ref(Tx);否则令其为 ref(U),执行 cjoin(Tx,U)
  • *x=y:若 Tx 尚无形状,赋予 ref(Ty);否则令其为 ref(U),执行 cjoin(U,Ty)

分配站点h提供一个独立位置标签及初始形状,令新指针目标与 Lh 合并。最终x的points-to集合由与 Tx 同类的所有位置标签读出。

直觉

Andersen允许p只指a、q只指b,而r指a或b。Steensgaard要求r只有一个目标代表,所以把a和b装进同一个位置类;此后任何指向该类的指针都可能指其中任一成员。

这会把一条新别名传播到原本较精确的其他指针,但不需要反复维护任意大的目标集合。等价类只能合并、不能拆开,有限代表数量给出很低的更新成本。

例子与边界

一个合并为何影响三个指针 ​

执行分析以下语句,顺序只用于展示求解过程,不作为流敏感杀死信息:

text
p = &a
q = &b
r = p
r = q

前两句令 Tp 与 La 同类,Tq 与 Lb 同类。因为变量位置本身都有ref形状,第三句的源目标 Tp 已非底,故把 Tr 与 Tp 合并;第四句同样把 Tr 与 Tq 合并。

于是 La,Lb,Tp,Tq,Tr 属于同一代表类,读出的结果为

pt(p)=pt(q)=pt(r)={a,b}.

相比之下,包含分析只让r得到两目标,p仍只有a、q仍只有b。额外目标来自等价类合并,不是程序真的把q的值写进了p。

变量p和q自己的位置代表 Lp,Lq 不因此必须合并。若还写 u=&p、v=&q,不应仅凭p、q目标相同就断言u、v都指向同一变量位置。目标层与容器层必须区分。

条件合并保留了什么 ​

先遇到 x=y,此时 Ty=⊥,算法只登记 Tx 等待 Ty。随后处理 x=&a,x获得目标a,但y仍无指针事实;原版不会仅因这次单向赋值就把a反向灌给y。

若后来出现 y=&b,Ty由底变为有形状,待办才触发,与已有的 Tx 合并,最终两者指向含a、b的同一类。这个例子说明条件合并保留部分方向信息,但一旦源端有了指针形状,合并仍会产生对称的粗化。

如果直接为每条赋值无条件join,第一种程序就会让y也指a。这依然可以是可靠的更粗分析,却不再是这里给出的原版条件合并结果。

自指针不是类型错误 ​

语句 x=&x 使 Tx 与 Lx 合并,从而出现形如 Lx=ref(Lx) 的循环形状。运行时自指针完全合法,分析应以有限循环图表示它,不能照搬有限类型树一阶统一里的occurs check并拒绝程序。

这里的“类型”是存储形状,不是源语言的Int、Bool等静态类型。只用ref一种形状构造器时,合并的职责是保守覆盖地址关系,不是发现通常意义上的类型不匹配。

推论与应用

一次赋值只产生常数个代表及合并约束,整个规范化程序产生 O(n) 个代表。每次真正join都减少等价类数量,所以最多线性次;递归合并子形状的成本可归到这些实际合并。配合路径压缩与按秩合并,核心求解为 O(nα(n)) 时间、O(n) 紧凑形状空间,其中 α 是增长极慢的反Ackermann函数。待办集合也须采用可高效合并和消费的表示,不能在每次join时复制整张大列表。

线性空间描述的是共享等价类图。若对每个变量显式打印其可能目标列表,许多变量可以指向同一个大类,输出总长仍可能为 Θ(n2);不能把紧凑解的成本当成完整展开报告的成本。

可靠性建立在存储形状对具体地址边的覆盖上。join只扩大类而不删地址,解引用规则确保目标单元的内容形状与读写源相容;条件合并在源端没有信息时延迟,但一旦有信息就执行全部必要合并,因此不会永久漏掉约束。

用于大程序的初筛时,较粗结果可能仍足以证明很多地址互不相关。若关键告警恰由大等价类造成,可以只对相关区域改用包含分析或更细上下文,而不是假定一种分析在所有精度/成本维度都占优。

参考资料
  • Bjarne Steensgaard, “Points-to Analysis in Almost Linear Time”, POPL, 1996, 32–41,§§3–5、Figures 5–6:存储形状、条件join、并查集求解与复杂度
  • Anders Møller and Michael I. Schwartzbach, Static Program Analysis,2026年8月版,§11.4:合一式表示;该教材明确省略原算法条件合并,本页保留此差别
关系图谱11 个相邻概念 · 3 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系