3

I created a minimal C++ program:

int main() {
    return 1234;
}

and compiled it with clang++5.0 with optimization disabled (the default -O0). The resulting assembly code is:

  pushq %rbp
  movq %rsp, %rbp
  movl $1234, %eax # imm = 0x4D2
  movl $0, -4(%rbp)
  popq %rbp
  retq

I understand most of the lines, but I do not understand the "movl $0, -4(%rbp)". It seems the program initializes some local variable to 0. Why?

What compiler-internal detail leads to this store that doesn't correspond to anything in the source?

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Erel Segal-Halevi
  • 33,955
  • 36
  • 114
  • 183
  • 5
    Because you forgot to enable optimization. Use `-O2`. – Jester Mar 15 '18 at 12:59
  • 1
    The “why” part is answered trivially by “because you compiled with optimizations turned off.” The compiler tends to generate weird code if you forbid it to optimize oddities away. – fuz Mar 15 '18 at 12:59
  • But why is this line there in the first place? – Erel Segal-Halevi Mar 15 '18 at 13:06
  • Because the compiler can do what it wants. It's probably some sort of sentinel for something. It could be used to detect buffer overflows. – NathanOliver Mar 15 '18 at 13:09
  • 1
    IDK, but we can narrow it down to `main` being "special". https://godbolt.org/g/fjYKjH. clang doesn't do it for functions with different names. Unfortunately `clang -fverbose-asm` doesn't label each line in as much detail as gcc, so we can't see why from that. Maybe if we looked at the LLVM-IR or other compiler internals for `-O0` mode. – Peter Cordes Mar 15 '18 at 13:09
  • 1
    @NathanOliver: A stack canary doesn't make sense. It's not reading it back, and it's in the red-zone below RSP. It looks nothing like what `gcc -fstack-protector-strong` does. It's probably some hidden variable, or maybe leftover from some kind of difference between how clang handles `main(void)` and `main(int argc, char**argv)`, with `main` being special. – Peter Cordes Mar 15 '18 at 13:11
  • 5
    It could be because apparently _"reaching the end of the main() function is equivalent to returning 0"_ ([source](https://stackoverflow.com/a/204483/547981)). So the compiler was just setting up for that case and due to optimizations off, it did not remove that code. – Jester Mar 15 '18 at 13:19
  • @Jester this is very interesting, but the return value of main is put in the register eax, not on the stack... – Erel Segal-Halevi Mar 15 '18 at 17:11

1 Answers1

7

TL;DR : In unoptimized code your CLANG++ set aside 4 bytes for the return value of main and set it to zero as per the C++(including C++11) standards. It generated the code for a main function that didn't need it. This is a side effect of not being optimized. Often an unoptimized compiler will generate code it may need, then doesn't end up needing it, and nothing is done to clean it up.


Because you are compiling with -O0 there is a very minimum of optimizations done on code (-O0 may remove dead code etc). Trying to understand artifacts in unoptimized code is usually a wasted exercise. The results of unoptimized code are extra loads and stores and other artifacts of raw code generation.

In this case main is special because in C99/C11 and C++ the standards effectively say that when reaching the outer block of main the default return value is 0. The C11 standard says:

5.1.2.2.3 Program termination

1 If the return type of the main function is a type compatible with int, a return from the initial call to the main function is equivalent to calling the exit function with the value returned by the main function as its argument;11) reaching the } that terminates the main function returns a value of 0. If the return type is not compatible with int, the termination status returned to the host environment is unspecified.

The C++11 standard says:

3.6.1 Main function

5) A return statement in main has the effect of leaving the main function (destroying any objects with automatic storage duration) and calling std::exit with the return value as the argument. If control reaches the end of main without encountering a return statement, the effect is that of executing

 return 0;

In the version of CLANG++ you are using the unoptimized 64-bit code by default has the return value of 0 placed at dword ptr [rbp-4].

The problem is that your test code is a bit too trivial to see how this default return value comes in to play. Here is an example that should be a better demonstration:

int main() {
    int a = 3;
    if (a > 3) return 5678;
    else if (a == 3) return 42;
}

This code has two exit explicit exit points via return 5678 and return 42; but there isn't a final return at the end of the function. If } is reached the default is to return 0. If we examine the godbolt output we see this:

main:                                   # @main
        push    rbp
        mov     rbp, rsp
        mov     dword ptr [rbp - 4], 0        # Default return value of 0
        mov     dword ptr [rbp - 8], 3
        cmp     dword ptr [rbp - 8], 3        # Is a > 3
        jle     .LBB0_2
        mov     dword ptr [rbp - 4], 5678     # Set return value to 5678
        jmp     .LBB0_5                       # Go to common exit point .LBB0_5
.LBB0_2:
        cmp     dword ptr [rbp - 8], 3        # Is a == 3?
        jne     .LBB0_4
        mov     dword ptr [rbp - 4], 42       # Set return value to 42
        jmp     .LBB0_5                       # Go to common exit point .LBB0_5
.LBB0_4:
        jmp     .LBB0_5                       # Extraneous unoptimized jump artifact 
# This is common exit point of all the returns from `main`
.LBB0_5:
        mov     eax, dword ptr [rbp - 4]      # Use return value from memory
        pop     rbp
        ret

As one can see the compiler has generated a common exit point that sets the return value (EAX) from the stack address dword ptr [rbp-4]. At the beginning of the code dword ptr [rbp-4] is explicitly set to 0. In the simpler case, the unoptimized code still generates that instruction but goes unused.

If you build the code with the option -ffreestanding you should see the default return value for main no longer set to 0. This is because the requirement for a default return value of 0 from main applies to a hosted environment and not a freestanding one.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198