4

I was playing around a bit to get a better grip on calling conventions and how the stack is handled, but I can't figure out why main allocates three extra double words when setting up the stack (at <main+0>). It's neither aligned to 8 bytes nor 16 bytes, so that's not why as far as I know. As I see it, main requires 12 bytes for the two parameters to func and the return value.

What am I missing?

The program is C code compiled with "gcc -ggdb" on a x86 architecture.

Edit: I removed the -O0 flag from gcc, and it made no difference to the output.

(gdb) disas main
Dump of assembler code for function main:
    0x080483d1 <+0>:    sub    esp,0x18
    0x080483d4 <+3>:    mov    DWORD PTR [esp+0x4],0x7
    0x080483dc <+11>:   mov    DWORD PTR [esp],0x3
    0x080483e3 <+18>:   call   0x80483b4 <func>
    0x080483e8 <+23>:   mov    DWORD PTR [esp+0x14],eax
    0x080483ec <+27>:   add    esp,0x18
    0x080483ef <+30>:   ret    
End of assembler dump.

Edit: Of course I should have posted the C code:

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

void main() {
    int x;
    x = func(3, 7);
}

The platform is Arch Linux i686.

Dumbfounded DM
  • 156
  • 1
  • 10
  • 1
    It might be helpful to post the C code – Mike Kwan Mar 25 '12 at 17:02
  • The platform would also be useful, since you are asking about calling conventions. Mac OS X's, for instance, requires the stack to be kept aligned on 16-bytes boundaries. – Pascal Cuoq Mar 25 '12 at 17:07
  • It's best to assume that when you disable optimizations that you'll end up looking at un-optimized code. – Hans Passant Mar 25 '12 at 17:49
  • @HansPassant Certainly, but it makes no difference in this particular case. I recompiled without it, and the disassembly is identical. – Dumbfounded DM Mar 25 '12 at 17:52
  • Hmm, that can't be right. You should only have `ret` left after optimization. The code has no side effects so can be completely eliminated. – Hans Passant Mar 25 '12 at 17:56
  • The default behaviour of gcc is no optimization, as in -O0, which is why it made no difference removing the flag. You need to explicitly state that you want optimization. – Dumbfounded DM Mar 25 '12 at 18:10

2 Answers2

5

The parameters to a function (including, but not limited to main) are already on the stack when you enter the function. The space you allocate inside the function is for local variables. For functions with simple return types such as int, the return value will normally be in a register (eax, with a typical 32-bit compiler on x86).

If, for example, main was something like this:

int main(int argc, char **argv) { 
   char a[35];

   return 0;
}

...we'd expect to see at least 35 bytes allocated on the stack as we entered main to make room for a. Assuming a 32-bit implementation, that would normally be rounded up to the next multiple of 4 (36, in this case) to maintain 32-bit alignment of the stack. We would not expect to see any space allocated for the return value. argc and argv would be on the stack, but they'd already be on the stack before main was entered, so main would not have to do anything to allocate space for them.

In the case above, after allocating space for a, a would typicaly start at [esp-36], argv would be at [esp-44] and argc would be at [esp-48] (or those two might be reversed -- depending on whether arguments were pushed left to right or right to left). In case you're wondering why I skipped [esp-40], that would be the return address.

Edit: Here's a diagram of the stack on entry to the function, and after setting up the stack frame:

enter image description here

Edit 2: Based on your updated question, what you have is slightly roundabout, but not particularly hard to understand. Upon entry to main, it's allocating space not only for the variables local to main, but also for the parameters you're passing to the function you call from main.

That accounts for at least some of the extra space being allocated (though not necessarily all of it).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • But main() only has three local ints, 12 bytes. So why does it allocate 24 bytes? – Dumbfounded DM Mar 25 '12 at 17:36
  • 1
    @spektre: The rest is padding, to align the stack to 16 bytes. Try modifying `func()` to use less/more arguments. You'll notice that `main()`'s stack area changes in 16-byte increases (with more arguments you'll eventually see `sub esp,0x28`, then with more arguments it will change to `sub esp,0x38`, ...). – ninjalj Mar 25 '12 at 20:48
  • @ninjalj Alignment was the first thing I thought of, but it didn't add up as I assumed `esp` already was aligned when entering `main()`. Although now that I think about it, alignment would never be necessary if it was. Why isn't it aligned from the start then? Surely the code running before entering `main()` uses the stack, or does that code strictly lie in another context or something? – Dumbfounded DM Mar 25 '12 at 21:03
3

It's alignment. I assumed for some reason that esp would be aligned from the start, which it clearly isn't.

gcc aligns stack frames to 16 bytes per default, which is what happened.

Dumbfounded DM
  • 156
  • 1
  • 10
  • The stack is aligned by 16 *before* a `call` instruction, so the args are 16B-aligned. IF the stack didn't have a known alignment before entry to `main`, gcc would emit code that used `and esp, -16` to align it. – Peter Cordes Dec 10 '17 at 17:56