← 学习中心

Knuth 的 bug 与程序正确性:为什么代码永远可能有错

《计算机程序设计艺术》作者高德纳的经典长除法算法,40 多年后才被发现藏着一个 bug。这引出软件工程最深的难题——程序正确性无法靠"多测试"保证。本文讲清 bug 的根源,以及形式化验证(Lean/Coq)如何用数学证明兜底。

2026 年 8 月,一条新闻在程序员社区刷了屏:有人在高德纳(Donald Knuth)的传世经典《计算机程序设计艺术》(TAOCP)第二卷的长除法算法里,发现了一个藏了几十年的 bug。连"计算机科学之父"亲手写下、又被无数人反复阅读过的代码都会出错——这件事本身就是软件工程最深刻的一课。

为什么程序一定有 bug?

程序的本质,是用有限的代码去描述无限多种输入下的行为。一个函数哪怕只有几个整数参数,可能的输入组合也是天文数字——你不可能把每一种都跑一遍。测试只能证明"有 bug",永远无法证明"没 bug":测过的路径越多,只能说明剩下的盲区越少,而不是归零。这正是高德纳长除法 bug 能潜伏几十年的原因:它只在某些极其特殊的输入组合下才会触发,恰好落进了所有人测试的盲区。

测试 vs 证明:两种"保证正确"的路线 测试(找反例) 只能证明「有 bug」 永远无法证明「没 bug」 盲区里可能藏着 40 年老 bug 证明(形式化验证) 把程序当作数学定理 用 Lean / Coq 机器检查证明 证明通过 = 所有输入都正确

图1:测试只能找反例,形式化证明才能覆盖全部输入

形式化验证:把程序当成定理来证明

有没有办法真正"证明"一段代码对所有输入都正确?有——形式化验证(formal verification)。思路是:把程序写成"定理",把需求写成"要证明的命题",然后用证明助手(如 Lean、Coq)一步步推导,由机器核对每一步是否正确。证明一旦通过,就等价于"这段代码在数学意义上绝对正确"——不是"测了很多次没出错",而是"逻辑上不可能出错"。

高德纳长除法 bug:藏在特殊输入里 算法 4.3.1D(多精度除法)只在极少数输入组合下才出错 绝大多数输入 → 结果正确(所以几十年没人发现) 特殊输入组合 → 触发边界 bug 教训:边界条件 + 特殊组合,是 bug 最爱的藏身处

图2:经典算法的 bug 往往只藏在极少数输入组合里

核心要点

  • 测试 ≠ 证明:测试只能找反例,无法穷尽所有输入。
  • bug 偏爱边界:特殊输入组合是 bug 的藏身处,高德纳的长除法就是例子。
  • 形式化验证:用 Lean/Coq 把程序当定理证明,证明通过即"逻辑上不可能错"。
  • 现实取舍:形式化代价高,关键系统(航空航天、金融、编译器)才值得全量证明;普通代码靠测试 + 边界审查兜底。