Arthur Araruna
Arthur Araruna

Reputation: 349

Why stack grows by 16 bytes in this disassembly, when I only have one 4 byte local variable?

I'm having trouble understanding why the compiler chose to offset the stack space in the way it did with the code I wrote.

I was toying with Godbolt's Compiler Explorer in order to study the C calling convention, when I came up with a simple code that puzzled me by its choices.

The code is found in this link. I selected GCC 8.2 x86-64, but am targetting x86 processors and this is important. Bellow is the transcription of the C code and the generated assembly reported by the Compiler Explorer.

// C code
int testing(char a, int b, char c) {
    return 42;
}

int main() {
    int x = testing('0', 0, '7');

    return 0;
}
; Generated assembly
testing(char, int, char):
        push    ebp
        mov     ebp, esp
        sub     esp, 8
        mov     edx, DWORD PTR [ebp+8]
        mov     eax, DWORD PTR [ebp+16]
        mov     BYTE PTR [ebp-4], dl
        mov     BYTE PTR [ebp-8], al
        mov     eax, 42
        leave
        ret
main:
        push    ebp
        mov     ebp, esp
        sub     esp, 16
        push    55
        push    0
        push    48
        call    testing(char, int, char)
        add     esp, 12
        mov     DWORD PTR [ebp-4], eax
        mov     eax, 0
        leave
        ret

Looking at the assembly column from now on, as I understood, line 15 is responsible for reserving space in the stack for the local variables. The problem is that I have only one local int and the offset was by 16 bytes instead of 4. This feels like wasted space.

Is this somewhat related to word alignment? But even if it is, if the sizes of the general purpose registers are 4 bytes, shouldn't this alignment be with regards to 4 bytes?

One other strange thing I see is with respect to the placement of the local chars of the testing function. They seem to be taking 4 bytes each in the stack, as seen in lines 7-8, but only the lower bytes are manipulated. Why not use only 1 byte each?

These choices are probably well intended, and I would really like to understand their purpose (or whether there is no purpose). Or maybe I'm just confused and didn't quite get it.

Upvotes: 3

Views: 640

Answers (2)

Luis Colorado
Luis Colorado

Reputation: 12635

It's common today to acquire stack space in multiples of this size, for several reasons:

  • cache lines favor this behaviour by maintaining the whole data in the cache.
  • space for temporaries is preallocated, avoiding push and pop instructions to be used in case some temporary storage is needed out of the cpu.
  • individual push and pop instructions degrade pipeline execution, by requiring data to be updated before next instruction is executed. This decouples the data dependencies between consecutive instructions and allow them to run faster.

For this reasons, actual compilers specify ABIs to be designed in this way.

Upvotes: 0

Arthur Araruna
Arthur Araruna

Reputation: 349

So, by the comments, I could figure out that the stack growth issue is due to the i386 SystemV ABI requirements, as stated by @PeterCordes.

The reason why the chars are word aligned may be due to GCC's default behavior to improve speed, as maybe inferenced from @Ped7g's comment. Although not definite, this is a good enough answer for me.

Upvotes: 3

Related Questions