1

I am debugging a simple code in c++ and, looking at the disassembly. In the disassembly, all the calculations are done in the registers. And later, the result of the operation is returned. I only see the a and b variables being pushed onto the stack (the code is below). I don't see the resultant c variable pushed onto the stack. Am I missing something?

I researched on the internet. But on the internet it looks like all variables a,b and c should be pushed onto the stack. But in my Disassembly, I don't see the resultant variable c being pushed onto the stack.

C++ code:

#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;
}

Relevant assembly code:

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  
}

As seen above, 14h and 0Ah are pushed onto the stack - corresponding to AddMe(10, 20);

But, when we look at the disassembly for the AddMe function, we see that the variable c (c = a + b), is not pushed onto the stack.

snippet of AddMe in Disassembly:

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]  
}

shouldn't c be pushed to the stack in this program? Am I missing something?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Vikram Singh
  • 435
  • 2
  • 10
  • 1
    I think what would help you is if you had the debugger not display symbolic names. To turn that off right mouse click anywhere in the assembly window and click `Show Symbol Names` to remove the checkmark and have the symbol names replaced by the actual memory operands. – Michael Petch Aug 25 '19 at 00:59

2 Answers2

4

All the calculations take place in registers.

Well yes, but they're stored afterwards.

Using memory-destination add instead of just using the accumulator register (EAX) would be an optimization. And one that's impossible when when the result needs to be in a different location than any of the inputs to an expression.

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

It is, just not with push


You compiled with optimization disabled (debug mode) so every C object really does have its own address in the asm, and is kept in sync between C statements. i.e. no keeping C variables in registers. (Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?). This is one reason why debug mode is extra slow: it's not just avoiding optimizations, it's forcing store/reload.

But the compiler uses mov not push because it's not a function arg. That's a missed optimization that all compilers share, but in this case it's not even trying to optimize. (What C/C++ compiler can use push pop instructions for creating local variables, instead of just increasing esp once?). It would certainly be possible for the compiler to reserve space for c in the same instruction as storing it, using push. But compilers instead to stack-allocation for all locals on entry to a function with one sub esp, constant.

Somewhere before the mov dword ptr [c],eax that spills c to its stack slot, there's a sub esp, 12 or something that reserves stack space for c. In this exact case, MSVC uses a dummy push to reserve 4 bytes space, as an optimization over sub esp, 4.

In the MSVC asm output, the compiler will emit a c = ebp-4 line or something that defines c as a text substitution for ebp-4. If you looked at disassembly you'd just see [ebp-4] or whatever addressing mode instead of.

In MSVC asm output, don't assume that [c] refers to static storage. It's actually still stack space as expected, but using a symbolic name for the offset.


Putting your code on the Godbolt compiler explorer with 32-bit MSVC 19.22, we get the following asm which only uses symbolic asm constants for the offset, not the whole addressing mode. So [c] might just be that form of listing over-simplifying even further.

_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
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
0

The __cdecl calling convention, which AddMe() uses by default (depending on the compiler's configuration), requires parameters to be passed on the stack. But there is nothing requiring local variables to be stored on the stack. The compiler is allowed to use registers as an optimization, as long as the intent of the code is preserved.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770