返回 2026-04-22
⚙️ 工程

为何异或操作成为清零寄存器的首选而非减法?Sure, xor’ing a register with itself is the idiom for zeroing it out, but why not sub?

尽管异或寄存器自身是清零寄存器的惯用方法,但作者探讨了为何不采用减法操作。文章分析了不同指令集架构下的性能差异和编译器优化策略,解释了异或操作在大多数处理器上具有更优的性能特征。

Raymond Chen

马特·戈德博尔(Matt Godbolt),最广为人知的是 Compiler Explorer 的创始人,曾撰写过一篇短文,解释了为何 x86 编译器如此钟爱 xor eax, eax 这条指令。

答案在于它是 x86 架构上将寄存器清零最紧凑的方式。特别是,它比更直观的 mov eax, 0 短几个字节,因为它无需编码四字节常量。x86 架构没有专用的零寄存器,因此若需将寄存器清零,必须从零开始操作。

但马特点明了为何大家选择 xor 而非其他任何能确保结果为零的数学运算?例如 sub eax, eax 有何不妥?它与 xor eax, eax 编码长度相同,执行周期也一致。甚至在标志位处理上表现更佳:

注意 xor eax, eax 会令 AF 标志位未定义,而 sub eax, eax 则会将其清零。

我不知道为何 xor 最终胜出,但我怀疑这只是“蜂群效应”所致。

在我的假设历史中,xor 和 sub 最初流行度相近,xor 因某种偶然因素略占上风——或许是因为它看起来更“聪明”。

当早期编译器开始使用 xor 来清零寄存器时,这便形成了滚雪球效应:人们看到编译器生成 xor 指令后心想:“这些编译器真聪明,他们一定掌握了我不知道的技巧。”既然我原本在 xor 和 sub 之间犹豫不决,这一微小线索就足以让我倾向 xor。

这种清零寄存器的惯用法盛行后,英特尔在指令解码前端加入了特殊的 xor r, r 检测和 sub r, r 检测机制,并将目标寄存器重命名为内部零寄存器,从而完全绕过该指令的执行。可以说,从某种意义上讲,这条指令“执行耗时零周期”。前端检测还能打破依赖链:通常 xor 或 sub 的输出依赖于其输入,但在这种特殊情况下(寄存器与自身进行 xor 或 sub 操作),我们已知输出恒为零,与输入无关。

尽管英特尔同时支持 xor-detection 和 sub-detection,Stack Overflow 仍担心其他 CPU 制造商可能只对 xor 做了特殊优化而未处理 sub,这使得 xor 在这场本无意义的竞争中成为赢家。

一旦某条指令获得哪怕极其微弱的优势,就足以改变局势,吸引所有人转向它。

额外闲聊:我的一位前同事偏爱使用 sub r, r 来清零寄存器,当我阅读汇编代码时,只要看到他用 sub 而非更流行的 xor 来清零寄存器,就能认出那是他的代码。

再补充一点:xor 技巧对 Itanium 无效,因为数学运算不会重置 NaT 位。所幸 Itanium 拥有专用零寄存器,因此无需此技巧,只需直接将零值移入目标寄存器即可。

分类

主题

作者

雷蒙德从事 Windows 演进工作已有三十余年。2003 年,他创办了一个名为 The Old New Thing 的网站,其受欢迎程度远超他当初的想象,这一发展至今仍让他感到些许不安。该网站催生了一本同名书籍《The Old New Thing》(Addison Wesley 2007)。他偶尔会在 Windows Dev Docs 的 Twitter 账号上出现,讲述一些毫无实用价值的故事。

需要完整排版与评论请前往来源站点阅读。