【发布时间】:2017-05-15 13:11:08
【问题描述】:
昨天我正在查看一些由 VC++ 2010 生成的 32 位代码(很可能;不知道具体的选项,抱歉),我对一个奇怪的反复出现的细节很感兴趣:在许多函数中,它归零了 @987654322 @ 在序言中,它总是像“零寄存器”一样使用它(想想 MIPS 上的 $zero)。特别是,它经常:
- 用它来清零内存;这并不罕见,因为
mov mem,imm的编码比mov mem,reg大 1 到 4 个字节(即使为 0,也必须对完整的立即值大小进行编码),但通常 (gcc) 必要的寄存器被清零“按需”,并保留用于其他更有用的目的; - 用它来与零进行比较 - 如
cmp reg,ebx。这是让我感到非常不寻常的地方,因为它应该与test reg,reg完全相同,但增加了对额外寄存器的依赖。现在,请记住,这发生在非叶函数中,ebx经常被(被调用者)推入和推出堆栈,所以我不相信这种依赖关系总是完全免费的。此外,它还以完全相同的方式使用了test reg,reg(test/cmp=>jg)。
最重要的是,“经典”x86 上的寄存器是一种稀缺资源,如果您开始不得不溢出寄存器,那么您会无缘无故地浪费大量时间;为什么要在所有函数中浪费一个只是为了在其中保留一个零? (不过,仔细想想,我不记得在使用这种“零寄存器”模式的函数中看到太多寄存器溢出)。
那么:我错过了什么?这是 2010 年特别有趣的编译器故障还是一些令人难以置信的智能优化?
摘录如下:
; standard prologue: ebp/esp, SEH, overflow protection, ... then:
xor ebx, ebx
mov [ebp+4], ebx ; zero out some locals
mov [ebp], ebx
call function_1
xor ecx, ecx ; ebx _not_ used to zero registers
cmp eax, ebx ; ... but used for compares?! why not test eax,eax?
setnz cl ; what? it goes through cl to check if eax is not zero?
cmp ecx, ebx ; still, why not test ecx,ecx?
jnz function_body
push 123456
call throw_something
function_body:
mov edx, [eax]
mov ecx, eax ; it's not like it was interested in ecx anyway...
mov eax, [edx+0Ch]
call eax ; virtual method call; ebx is preserved but possibly pushed/popped
lea esi, [eax+10h]
mov [ebp+0Ch], esi
mov eax, [ebp+10h]
mov ecx, [eax-0Ch]
xor edi, edi ; ugain, registers are zeroed as usual
mov byte ptr [ebp+4], 1
mov [ebp+8], ecx
cmp ecx, ebx ; why not test ecx,ecx?
jg somewhere
label1:
lea eax, [esi-10h]
mov byte ptr [ebp+4], bl ; ok, uses bl to write a zero to memory
lea ecx, [eax+0Ch]
or edx, 0FFFFFFFFh
lock xadd [ecx], edx
dec edx
test edx, edx ; now it's using the regular test reg,reg!
jg somewhere_else
注意:这个问题的早期版本说它使用mov reg,ebx 而不是xor ebx,ebx;这只是我没有正确记住东西。对不起,如果有人想太多试图理解这一点。
【问题讨论】:
-
不等了,可能昨天太累了,完全不像我上面说的那样。我现在再次检查,虽然它确实将整个函数的
ebx保持为零(这让我觉得很不寻常),但它使用它来清零 memory,其中编码为mov更紧凑(与使用立即零相比),对于cmp,即使是针对寄存器 - 这再次让我觉得不寻常,因为cmp reg,ebx(与ebx==0)应该相同就像普通的test reg,reg。我会相应地更新问题。 -
xD,应该点击展开在输入我的答案时弹出的评论:P 是的,
test reg,regsets flags the same as acmpagainst zero(立即或注册),除了未定义 AF。性能方面,test可以在更多情况下在更多 CPU 上进行宏融合,并避免读取死寄存器(有助于 P6,因此早在 1993 年就适用)。 -
编译器可能正在优化大小而不是速度。
-
正如您的问题似乎暗示的那样,它并没有始终如一地这样做 - 至少在优化的代码中没有。我已经针对大量不同的代码序列详细研究了 VS 2010 的编译器生成的目标代码,但从未注意到这种模式。因此,要么您遇到了一些优化器确实认为这是有道理的边缘情况,要么您正在查看未优化的代码。不过,总体而言,您是对的,Microsoft 的编译器通常会使用零寄存器来将内存归零,因为这样更快更小,除非在非常高的寄存器压力条件下。
-
您能否发布一些您为查看这些模式而编译的代码示例?
标签: visual-c++ assembly x86 visual-c++-2010