为什么我要使用 PUSH ecx?

6

请问下面的两个push ecx指令的目的是什么?我不理解它们应该要做什么。

我知道push ebp是保存堆栈基指针,然后mov ebp, esp是将堆栈指针赋值给以前的堆栈基指针。

int main(){
01301190  push        ebp  
01301191  mov         ebp,esp  
01301193  push        ecx                              ;here?? 
01301194  mov         dword ptr [h],0CCCCCCCCh  
    int h = my_func(1,3);



int my_func(int a, int b){
01301160  push        ebp  
01301161  mov         ebp,esp  
01301163  push        ecx                              ;here??
01301164  mov         dword ptr [m],0CCCCCCCCh  
    int m = 0;
0130116B  mov         dword ptr [m],0  
    m = a*b;
01301172  mov         eax,dword ptr [a]  
01301175  imul        eax,dword ptr [b]  
01301179  mov         dword ptr [m],eax  
    return m;
0130117C  mov         eax,dword ptr [m]  
}
0130117F  mov         esp,ebp  
}
01301181  pop         ebp  
01301182  ret  

它显然不是所有的代码,"push cx" 可以用于保护它免受缺失部分的修改。 - Iłya Bursov
@Lashane,看起来my_func的所有内容都在那里了。我认为这个问题是公平的。 - Dwayne Towell
4个回答

11

push ecx指令在堆栈上为本地变量(mh)分配4字节。实际的ecx内容并不重要——分配的插槽立即被0CCCCCCCCh覆盖(这个神奇的值用于Visual C++在调试版本中标记未初始化的变量)。

Visual C++经常使用push ecxpop ecx作为sub esp, 4add esp, 4的替代方案。为什么呢?原因有几点:

push ecxpop ecx是单字节指令,而addsub各需三个字节。虽然差别可能不算很大,但所有函数中保存的所有字节可能会积累成相当可观的数字。

ecx被认为已在函数调用中受到破坏,因此可以安全地使用pop ecx将其清除。因此,你经常会看到以下代码:

  push arg1    ; push an argument
  call __func1 ; call the function
  pop ecx      ; restore the stack

对于push指令,没有实际的理由特别使用ecx寄存器 - 任何基本寄存器都可以。我猜只是为了对称性或者不要与真正保存非易失性寄存器(如esiedi)混淆而选择了它。


1
在上面的汇编代码中,看到abm的定义将非常有益。我猜它们是ebp+8ebp+12ebp-4。如果是这样,编译器没有为调试器生成任何特殊代码,只是以最直接的方式生成代码。通过推送ecx创建的位置是为局部变量m创建的内存位置。

这些是C++函数的本地变量,包括两个参数吗? - user997112
没错,但是 mov eax,dword ptr [a] 不是一个字面上的 80x86 指令。a 必须被替换为类似于 ebp+8 的东西。在代码中应该有一行类似于 a equ ebp+8 的语句来定义 a - Dwayne Towell

0

我假定你已经关闭了优化功能。

编译器在调用函数之前会推送ECX寄存器。这样做是因为ECX是一个易失性寄存器,编译器希望确保在返回函数调用时其内容得到恢复。

在函数内部,它会推送ECX以便调试器可以轻松读取传递给函数的参数。

优化代码的反汇编通常更易于理解。


1
除非您使用 gcc 的疯狂优化级别 -O3,否则您理解的机会会大大降低 :-) - paxdiablo
ECX寄存器与其他寄存器不同吗?我只在维基百科上看到它通常用于循环计数。 - user997112
还有其他易失性寄存器,例如EAX和EDX。ECX很特殊,因为它被用作函数的第一个整型参数,在这种情况下是a,但通常是this指针。 - thisisdog
1
my_func 内没有任何函数调用。此外,ECX 仅用于 __thiscall__fastcall 函数的参数传递,而这里不是这种情况。在粘贴的代码中,所有参数都通过堆栈传递。 - Igor Skochinsky

0
在调试模式下,可能会有一个循环来填充本地内存变量的一些特殊模式,比如0CCCCCCCCh,并且这个循环通常使用ecx。在这些情况下,循环不存在。查看代码,模型将所有参数都传递到堆栈上(而不是使用ecx、edx来表示两个参数),因此没有真正的理由保存ecx。所以push ecx只是编译器留下的垃圾,因为它从未使用ecx或者用pop“恢复”ecx。

蚂蚁问这个0CCCCCCCCh是什么? - user997112
@user997112 在malloc、free、new等操作时,为什么操作系统会将内存初始化为0xcd、0xdd等值?这是什么原因呢? - phuclv
0CCCCCCCCh通常用于检查未初始化的指针,如果您使用该值的指针,它应该会引发异常。 - rcgldr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接