3

I have some C code that I compiled with gcc:

int main() {
    int x = 1;
    printf("%d\n",x);
    return 0;
}

I've run it through gdb 7.9.1 and come up with this assembler code for main:

0x0000000100000f40 <+0>:    push   %rbp                   # save original frame pointer
0x0000000100000f41 <+1>:    mov    %rsp,%rbp              # stack pointer is new frame pointer
0x0000000100000f44 <+4>:    sub    $0x10,%rsp             # make room for vars
0x0000000100000f48 <+8>:    lea    0x47(%rip),%rdi        # 0x100000f96
0x0000000100000f4f <+15>:   movl   $0x0,-0x4(%rbp)        # put 0 on the stack
0x0000000100000f56 <+22>:   movl   $0x1,-0x8(%rbp)        # put 1 on the stack
0x0000000100000f5d <+29>:   mov    -0x8(%rbp),%esi
0x0000000100000f60 <+32>:   mov    $0x0,%al
0x0000000100000f62 <+34>:   callq  0x100000f74
0x0000000100000f67 <+39>:   xor    %esi,%esi              # set %esi to 0
0x0000000100000f69 <+41>:   mov    %eax,-0xc(%rbp)
0x0000000100000f6c <+44>:   mov    %esi,%eax
0x0000000100000f6e <+46>:   add    $0x10,%rsp             # move stack pointer to original location
0x0000000100000f72 <+50>:   pop    %rbp                   # reclaim original frame pointer
0x0000000100000f73 <+51>:   retq  

As I understand it, push %rbb pushes the frame pointer onto the stack, so we can retrieve it later with pop %rbp. Then, sub $0x10,%rsp clears 10 bytes of room on the stack so we can put stuff on it.

Later interactions with the stack move variables directly into the stack via memory addressing, rather than pushing them onto the stack:

movl $0x0, -0x4(%rbp)
movl $0x1, -0x8(%rbp)

Why does the compiler use movl rather than push to get this information onto the stack?

Does referencing the register after the memory address also put that value into that register?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Grant Gordon
  • 31
  • 1
  • 3
  • possible duplicate of [why does it use the movl instead of push?](http://stackoverflow.com/questions/4534791/why-does-it-use-the-movl-instead-of-push) – ninjalj Jul 27 '15 at 20:39
  • Another dup, with a nice explanation: http://stackoverflow.com/questions/23309620/c-function-call-convention-why-movl-instead-of-pushl – ninjalj Jul 27 '15 at 21:01
  • 3
    `sub $0x10,%rsp` adjusts the stack pointer by 16 bytes, not 10. It also doesn't "clear" the space, just "allocates" it. – Peter Cordes Jul 27 '15 at 21:06

3 Answers3

4

It is very common for modern compilers to move the stack pointer once at the beginning of a function, and move it back at the end. This allows for more efficient indexing because it can treat the memory space as a memory mapped region rather than a simple stack. For example, values which are suddenly found to be of no use (perhaps due to an optimized shortcutted operator) can be ignored, rather than forcing one to pop them off the stack.

Perhaps in simpler days, there was a performance reason to use push. With modern processors, there is no advantage, so there's no reason to make special cases in the compiler to use push/pop when possible. It's not like compiler-written assembly code is readable!

Cort Ammon
  • 10,221
  • 31
  • 45
  • 2
    More like: `push/pop` to save/restore registers that you don't want to modify. `sub $0x10, %rsp` to allocate space for locals, and then `mov` to store/reload them. I don't think putting locals on the stack with `push` was ever a thing. `push` does come in handy to put function args on the stack, of course. (And then `add $32, esp` to pop (actually discard) 32bytes of pushed args, after the call.) – Peter Cordes Jul 27 '15 at 21:04
2

While Cort is correct, there is another important reason for this practice of apparently allocating space on the stack. According to the ABI, function calls must find the stack 16 byte aligned. Rather than fiddling with the stack every single time a call needs to be made from a function, it is generally easier and more efficient to adjust the stack for proper alignment first and then modify the values that might otherwise have been pushed onto it.

So, the stack is absolutely adjusted for local variable space, but it is also adjusted to provide correct stack alignment for calls into the standard library.

David Hoelzer
  • 15,862
  • 4
  • 48
  • 67
  • "The ABI"? You mean the System V ABI. Mind you, there are others, and one of the most ubiquitous x86 OS-es doesn't use it. – Rudy Velthuis Jul 28 '15 at 12:54
  • We aren't talking x86 here, we're talking x86_64. Yes, I mean the 64 bit System V ABI ( http://www.x86-64.org/documentation/abi.pdf ) though there are other more comprehensive references that expand on this same principle (https://msdn.microsoft.com/en-US/library/ms235286.aspx). The point is that there will be a defined stack alignment, in my experience this is *definitely* done in X86_64 APIs, and this can often account for the compiler's decision to include an alignment operation in the preamble that also accounts for local variable storage. – David Hoelzer Jul 28 '15 at 15:36
0

I'm not an authority on assemblers or compilers but I've played around with MASM back in the day and did spend a whole bunch of time with WinDbg while debugging production C++ issues.

I think the answer to your question is because it's easier.

push/pop instructions write to and read from the stack but they also modify the stack as they are processed. C/C++ compiler uses stack for all its local variables. It does it by shifting stack pointer by the exact number of bytes that is needed to hold all local variables and it does so right when you enter the function.

After that reading and writing all those variables can be done from anywhere in the function and also as many times as you want by simply using the mov instructions. If you look at pure assembly, you might question why create a hole in the stack just to copy two values into that space using mov when you could have done two push instructions.

But look at it from compiler author perspective. The process of entering a function, and allocating stack for local variables is coded separately and is completely decoupled from the process of reading/writing those variables.

DXM
  • 4,413
  • 1
  • 19
  • 29
  • Actually, gcc supports both kinds of modes of operation, PUSH for short code size, MOV for avoiding dependencies on ESP. – ninjalj Jul 27 '15 at 20:42