volatile的九宗罪:C标准抽象机与优化器的隐形战争
原创volatile的九宗罪:C标准抽象机与优化器的隐形战争
C标准抽象机与编译器的'as if'规则
第一次看到那段汇编时,我盯着屏幕愣了一会儿。函数明明写了一个循环,局部变量 i 从 0 走到 2,每轮把参数 x 加一,可编译器只给了三条指令。循环没了,变量没了,剩下一个加法。我查了 C 标准才明白,自己理解错了"C 程序是什么意思"。
C 标准把程序语义定义在一台抽象机上,你可以把它想成一个简单、不做优化的 C 解释器 来源:Regehr。任何实现,编译器加目标机器,必须产生与抽象机相同的副作用,除此之外,标准对抽象机与实际目标平台上的计算之间几乎不作要求 来源:Regehr。换句话说,C 程序是一份期望效果的规格说明,具体怎么实现,实现者自己决定。
拿 loop_add3 这个函数来说,抽象机的行为清楚可见。它创建函数级变量 i,从 0 循环到 2,每轮把 x 加一 来源:Regehr。C 代码长这样。
int loop_add3 (int x) {
int i;
for (i=0; i<3; i++) x++;
return x;
}而一个好的编译器生成这样的代码。
loop_add3:
movl 4(%esp), %eax
addl $3, %eax
ret三条指令,完成全部工作。i 在真实机器上从未被递增,甚至从未被实例化,但净效果与抽象机完全一致 来源:Regehr。
这个比喻让我想到一个老问题,编译器到底在替谁干活?C 标准说,它在替抽象机干活。目标平台上的寄存器、内存、指令调度,都是实现细节,标准允许实现随便折腾,只要最后的副作用对得上。
优化器自由度的真相:变量可被完全消除
这个例子让我意识到,抽象语义与实际语义之间的空隙,是标准有意留给优化器的。标准用"as if"措辞,允许实现产生与抽象机相同副作用的同时,自由选择实际计算方式 来源:Regehr。这个自由度就是优化器高效生成代码的许可证。
在 loop_add3 里,i 在真实机器上从未被递增或实例化,但净效果与抽象机完全一致 来源:Regehr。一个带循环和局部变量的函数,最终编译成直接加常量,循环体消失。这个结果反直觉,但标准允许,甚至鼓励。
作者在文章里明确说,抽象语义与实际语义之间的这个空隙,被认为是一件好事 来源:Regehr。我同意。没有这个空隙,编译器就没法做强度削减、常量折叠、死代码消除,我们写 C 的每一行都会变成真实机器上的逐条解释。
但这也带来一个代价:当你写的代码需要实际机器上的特定行为时,抽象机帮不上忙。C 是低级语言,为操作系统而生 来源:Regehr。写 OS 代码时,程序员经常需要把抽象语义和实际语义之间的空隙缩小或关闭 来源:Regehr。页表就是一个例子,C 的抽象语义没有捕捉到程序员需要一个真正位于 RAM 里的页表 来源:Regehr。
volatile:ugly blunt tool 的定位与HN冷遇
volatile 就是为了这些场合存在的。Regehr 说它有点像 C 预处理器,一个丑陋、钝的工具 来源:Regehr。它在一组非常窄的情况下能完成任务,但极其容易误用 来源:Regehr。文章没有直接教怎么用,而是通过一系列"不该这么用"的例子,反向教读者如何用 volatile 正确创建系统软件 来源:Regehr。
有意思的是,这篇文章在 Hacker News 上只得到 1 点、0 条评论 来源:HN。标题是"用 volatile 破坏系统代码的九种方式",气势汹汹,结果门可罗雀。我猜大多数系统程序员觉得自己知道 volatile 是怎么回事,不需要别人教。
文章聚焦 C,但 Regehr 提醒,几乎一切内容同样适用于 C++ 来源:Regehr。
我的判断是,volatile 最危险的地方,是它工作的方式太隐晦。它实际做的事,往往跟你想的不一样。写系统代码,volatile 绕不开,用之前,把它当成一个危险的钝器来对待。Regehr 的文章写于 2010 年,十几年过去,volatile 还是那个样子,丑钝,偶尔救命。
原文:
Nine ways to break your systems code using volatile (2010) · 来源:Hacker News
作者:itfan123 值班编辑 · 发布于 2026年11月2日
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除
itfan123




