为什么VC ++编译器MOV + PUSH而不仅仅是推动它们? 86

时间:2012-10-29 15:20:13

标签: visual-c++ assembly compiler-construction x86

在VC ++的反汇编中,正在进行函数调用。在推送寄存器之前,编译器将本地指针MOV转换为寄存器:

    memcpy( nodeNewLocation, pNode, sizeCurrentNode );
0041A5DA 8B 45 F8             mov         eax,dword ptr [ebp-8]  
0041A5DD 50                   push        eax  
0041A5DE 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]  
0041A5E1 51                   push        ecx  
0041A5E2 8B 55 D4             mov         edx,dword ptr [ebp-2Ch]  
0041A5E5 52                   push        edx  
0041A5E6 E8 67 92 FF FF       call        00413852  
0041A5EB 83 C4 0C             add         esp,0Ch 

为什么不直接推它们?即

push  dword ptr [ebp-8]

另外,如果您要单独进行推送,为什么不手动执行。换句话说,不要做上面的“推送eax”,而是

mov [esp], eax

等。这样做的好处是,在完成3个mov之后你可以做一个减法来设置新的堆栈指​​针,而不是通过推送隐式减去三次。

更新---发布版本

这与为发布而编译的代码相同:

; 741  :    memcpy( nodeNewLocation, pNode, sizeCurrentNode );

  00087 8b 45 f8     mov     eax, DWORD PTR _sizeCurrentNode$[ebp]
  0008a 8b 7b 04     mov     edi, DWORD PTR [ebx+4]
  0008d 50       push    eax
  0008e 56       push    esi
  0008f 57       push    edi
  00090 e8 00 00 00 00   call    _memcpy
  00095 83 c4 0c     add     esp, 12            ; 0000000cH

绝对比调试版本更有效,但它仍然在进行MOV / PUSH组合。

3 个答案:

答案 0 :(得分:5)

这是一项优化。英特尔处理器手册第4卷第12.3.3.6节明确提到:

  

在Intel Atom微体系结构中,使用PUSH / POP指令管理堆栈空间   函数调用/返回之间的地址调整将比   使用ENTER / LEAVE替代品。这是因为PUSH / POP不需要MSROM   流程和堆栈指针地址更新在AGU完成。   当被调用者函数需要返回给调用者时,被调用者可以发出POP指令   从EBP恢复数据并恢复堆栈指针。

     

汇编/编译器编码规则19.(MH影响,M一般性)对于Intel   Atom处理器,赞成PUSH / POP的注册形式并避免使用LEAVE;使用LEA   调整ESP而不是ADD / SUB。

手册的其余部分并不清楚原因,但确实提到了隐式ESP调整可能的3周期AGU失速。

答案 1 :(得分:1)

我怀疑它只在调试版本中执行,或者可能在某些情况下通过流水线操作或其他注意事项保证(例如,它可以将参数放入esi并在调用后使用它)。我查看了一些二进制文件,MSVC确实使用了这些推送:

 push ebx          ; mthd
 push dword ptr [ebp+place+4]
 push dword ptr [ebp+place] ; pos
 push [ebp+filedes]   ; fh
 call __lseeki64_nolock

(来自CRT的代码)

关于第二个问题,处理esp的指令比推送时长:"push eax"是一个字节,而"mov [esp-8], eax"四个字节。实际上,这种方法(mov而非push)默认情况下由GCC使用,因为几个版本之前(选项-maccumulate-outgoing-args)并且它已导致notable increases in code size。据说它使代码更快,但我不相信。

答案 2 :(得分:1)

我实际上弄明白了它的原因。它与Pentium MMX上的指令流水线方式有关。有两个流水线U和V,它们允许MMX处理器一次处理2条指令 IF 它们是可配对的。 PUSH彼此不可配对,但它们可与MOV配对。所以,如果你写:

mov eax, [indirect]
mov esi, [indirect]
push eax
push esi
然后,会发生的事情是指令#1和#3配对,#2和#4配对,因此,有效地,这四个指令的运行周期与单个mov / push相同,并且单个mov / push比两个push [indirect] s快。第4.3节第4节详细介绍了这种确切的情况。 41,Agner Fog的微架构优化指南的例子4.11a和4.11b,可在互联网上广泛获得。