在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组合。
答案 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,可在互联网上广泛获得。