在此拆卸VC 中,正在进行函数调用。编译器将本地指针移至寄存器之前:
: 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/PUST组合。
这是一种优化。它在英特尔处理器手册中明确提及,第4卷,第12.3.3.6节:
在Intel Atom Microharchitecture中 和函数呼叫/退货之间的地址调整将比 使用Enter/离开替代方案。这是因为PUSH/POP不需要MSROM 流和堆栈指针地址更新是在AGU上完成的。 当Callee功能需要返回呼叫者时,Callee可以发出POP指令 恢复数据并从EBP恢复堆栈指针。
组装/编译器编码规则19.(MH Impact,M Generality)Intel Atom处理器,偏爱按/pop 的注册形式,并避免使用休假;使用lea 调整ESP而不是添加/sub。
本手册的其余部分并不清楚原因,但是它确实提到了可能的3个周期Agu摊位,请在隐式ESP调整上进行。
我怀疑它只能在调试构建中或在某些情况下由管道或其他考虑因素保证(例如,它可以将参数放入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"
是四字节。实际上,默认情况下,GCC使用了这种方法(mov
而不是push
),因为几个版本以前(选项-maccumulate-outgoing-args
),它已导致代码大小的显着增加。据说它使代码更快,但我不相信。
我实际上弄清楚了它的原因。这与奔腾MMX上的说明方式有关。有两个管道U和V,允许MMX处理器一次处理2个说明如果它们是可配对的。推动与彼此不可配对,但可以与Movs配对。因此,如果您写:
mov eax, [indirect]
mov esi, [indirect]
push eax
push esi
那么,发生的是指令#1和#3配对,#2和#4进行配对,因此,有效地,这四个指令以与单个MOV/推动相同数量的周期运行,而单个MOV/推高比两个推动[间接] s快。第4.3节中详细描述了此确切情况。41,示例4.11a和4.11b,《 Agner Fog》的微观结构优化指南,可在Internet上广泛使用。