所有计算都在寄存器中进行。为什么堆栈不在这里存储寄存器计算的结果

All the calculations take place in registers. Why is the stack not storing the result of the register computation here

我正在用 C++ 调试一个简单的代码,并查看反汇编。 在反汇编中,所有的计算都是在寄存器中完成的。稍后,返回操作结果。我只看到 a 和 b 变量被压入堆栈(代码如下)。我没有看到将结果 c 变量压入堆栈。我错过了什么吗?

我上网查了一下。但在互联网上,看起来所有变量 a、b 和 c 都应该被压入堆栈。但是在我的反汇编中,我没有看到结果变量 c 被压入堆栈。

C++代码:

#include<iostream>
using namespace std;

int AddMe(int a, int b)
{
    int c;
    c = a + b;
    return c;
}

int main() 
{
    AddMe(10, 20);
    return 0;
}

相关汇编代码:

int main() 
{
00832020  push        ebp  
00832021  mov         ebp,esp  
00832023  sub         esp,0C0h  
00832029  push        ebx  
0083202A  push        esi  
0083202B  push        edi  
0083202C  lea         edi,[ebp-0C0h]  
00832032  mov         ecx,30h  
00832037  mov         eax,0CCCCCCCCh  
0083203C  rep stos    dword ptr es:[edi]  
0083203E  mov         ecx,offset _E7BF1688_Function@cpp (0849025h)  
00832043  call        @__CheckForDebuggerJustMyCode@4 (083145Bh)  
    AddMe(10, 20);
00832048  push        14h  
0083204A  push        0Ah  
0083204C  call        std::operator<<<std::char_traits<char> > (08319FBh)  
00832051  add         esp,8  
    return 0;
00832054  xor         eax,eax  
}

如上所示,14h 和 0Ah 被压入堆栈 - 对应于 AddMe(10, 20);

但是,当我们查看 AddMe 函数的反汇编时,我们看到变量 c (c = a + b) 没有被压入堆栈。

反汇编中的 AddMe 片段:

int c;
    c = a + b;
00836028  mov         eax,dword ptr [a]  
0083602B  add         eax,dword ptr [b]  
0083602E  mov         dword ptr [c],eax  
    return c;
00836031  mov         eax,dword ptr [c]  
}

这个程序中c是不是应该压栈?我错过了什么吗?

__cdecl调用约定,AddMe()默认使用(取决于编译器的配置),需要参数在堆栈上传递。但是不需要将局部变量存储在堆栈中。只要保留代码的意图,编译器就可以使用寄存器作为优化。

All the calculations take place in registers.

是的,但它们是在之后存储的。

使用内存目标 add 而不是仅使用累加器寄存器 (EAX) 将是一种优化。当结果需要位于与表达式的任何输入不同的位置时,这是不可能的。

Why is the stack not storing the result of the register computation here

是的,只是与 push

不同

您在禁用优化(调试模式)的情况下进行编译,因此每个 C 对象在 asm 中确实有自己的地址,并且在 C 语句之间保持同步。即不将 C 变量保存在寄存器中。 (Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?)。这是调试模式特别慢的原因之一:它不仅仅是避免优化,它还强制 store/reload.

但是编译器使用 mov 而不是 push 因为它不是函数参数。这是所有编译器共享的错过的优化,但在这种情况下,它甚至没有尝试优化。 ()。编译器当然有可能在存储它的同一指令中使用 pushc 保留 space。但是编译器改为在一个函数的入口处为所有局部变量分配堆栈 sub esp, constant.

在溢出 c 到其堆栈槽的 mov dword ptr [c],eax 之前的某处,有一个 sub esp, 12 或为 [= 保留堆栈 space 的东西15=]. 在这种情况下,MSVC 使用虚拟 push 保留 4 个字节 space,作为对 sub esp, 4.

的优化

在 MSVC asm 输出中,编译器将发出 c = ebp-4 行或将 c 定义为 ebp-4 的文本替换的内容。如果您查看反汇编,您只会看到 [ebp-4] 或任何寻址模式而不是。

在 MSVC asm 输出中,不要假设 [c] 指的是静态存储。 它实际上仍然是预期的堆栈 space,但是使用了偏移量的符号名称。


使用 32 位 MSVC 19.22 将您的代码放在 Godbolt compiler explorer 上,我们得到以下 asm,它仅使用符号 asm 常量作为偏移量,而不是整个寻址模式。所以 [c] 可能只是进一步简化的列表形式。

_c$ = -4                                                ; size = 4
_a$ = 8                                       ; size = 4
_b$ = 12                                                ; size = 4
int AddMe(int,int) PROC                                    ; AddMe
        push    ebp
        mov     ebp, esp                        ## setup a legacy frame pointer
        push    ecx                             # RESERVE 4B OF STACK SPACE FOR c

        mov     eax, DWORD PTR _a$[ebp]
        add     eax, DWORD PTR _b$[ebp]         # c = a+b
        mov     DWORD PTR _c$[ebp], eax         # spill c to the stack

        mov     eax, DWORD PTR _c$[ebp]         # reload it as the return value

        mov     esp, ebp                        # restore ESP
        pop     ebp                             # tear down the stack frame
        ret     0
int AddMe(int,int) ENDP                                    ; AddMe