minecraftplayer1234
minecraftplayer1234

Reputation: 2227

Why does the compiler make space on the stack

I don't know if my title is appropriate, cause my problem is: I know sometimes (f.e when I want to make use of argv[]) compiler has to arrange space on the stack for the command line arguments. Now I wrote a simple program just to see how the C compiler handles simple C arrays (actually it handles them the same way as std::array's). I'm using Manjaro Linux 64 bit. C code looks like this:

#include <stdio.h>

int main(){
    int a[5] = {1,2,3,4,5};
    //printf("%d", a[1]);
    return 0;
}

The assembly generated output (from gcc main.c -fno-asynchronous-unwind-tables -o XD.s -S):

    .file   "main.c"
    .text
    .globl  main
    .type   main, @function
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $1, -32(%rbp)
    movl    $2, -28(%rbp)
    movl    $3, -24(%rbp)
    movl    $4, -20(%rbp)
    movl    $5, -16(%rbp)
    movl    $0, %eax
    popq    %rbp
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 6.3.1 20170109"
    .section    .note.GNU-stack,"",@progbits

Now, when I uncomment printf statement, the code looks like this:

    .file   "main.c"
    .section    .rodata
.LC0:
    .string "%d"
    .text
    .globl  main
    .type   main, @function
main:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $32, %rsp
    movl    $1, -32(%rbp)
    movl    $2, -28(%rbp)
    movl    $3, -24(%rbp)
    movl    $4, -20(%rbp)
    movl    $5, -16(%rbp)
    movl    -28(%rbp), %eax
    movl    %eax, %esi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 6.3.1 20170109"
    .section    .note.GNU-stack,"",@progbits

The middle part is obvious, just calling printf. But why did compiler put a subq $32, %rsp line here? Why doesn't it appear in the first example, without printf statement?

Upvotes: 1

Views: 1084

Answers (4)

Ajay Brahmakshatriya
Ajay Brahmakshatriya

Reputation: 9203

This is an optimization your compiler does. It realizes in the first case that main is a leaf function and hence it knows that the array would be safe on the stack. Whereas in the second case, the call to printf would overwrite the stack frame and hence it protects it by incrementing the %rsp.

Actually there are many such optimizations compilers do. For instance the ABI specifies that the stack must be 16 bytes aligned before call and the frame created in such a way that the %rsp is 16 bytes aligned. But in case the function doesn't call any other function or doesn't use any SSE instructions (one example of instruction which requires aligned stack frame) it breaks the ABI requirements. These are really just micro optimizations done to save every byte possible.

Upvotes: 5

Ped7g
Ped7g

Reputation: 16596

I added comments to each line of assembly.

main:
    pushq   %rbp            ; save old stack frame
    movq    %rsp, %rbp      ; rbp = stack frame of this function
    subq    $32, %rsp       ; 32 bytes reserved on stack for local variable(s)
    movl    $1, -32(%rbp)   ; a[0] = 1 (at rbp-32 == rsp)
    movl    $2, -28(%rbp)   ; a[1] = 2
    movl    $3, -24(%rbp)   ; a[2] = 3
    movl    $4, -20(%rbp)   ; a[3] = 4
    movl    $5, -16(%rbp)   ; a[4] = 5 (rbp-16 == rsp+16)
        ; remaining 12B from rbp-12 to rbp-1 is not used ("wasted")
        ; but it works as "padding" to have correctly aligned rsp for printf
    movl    -28(%rbp), %eax ; eax = a[1]
    movl    %eax, %esi      ; esi = a[1] (argument for printf)
    movl    $.LC0, %edi     ; edi = format string pointer
    movl    $0, %eax        ; eax = 0 (zero FP/SSE arguments)
    call    printf
    movl    $0, %eax        ; return value of main
    leave                   ; restore stack frame and exit main
    ret

Upvotes: 4

You should compile your (real) code with gcc -S -fverbose-asm -O if you want to look into the generated .s assembler file.

Notice that recent ABI and calling conventions require the stack pointer to be 16 byte aligned at least (in particular, for compatibility with AVX or SSE). Read also about the Red Zone (as suggested by Zang Ming Jie).

But why did compiler put a subq $32, %rsp line here? Why doesn't it appear in the first example, without printf statement?

Probably because without any calls to printf your main has become a leaf routine. So the compiler don't need to update %rsp to be ABI compliant (in the called printf call frame).

Upvotes: 4

Zang MingJie
Zang MingJie

Reputation: 5275

rsp is used to pass frame point to inner calls, if a function doesn't call other function, it don't need to adjust rsp offset.

NOTE: There is a 128-byte area beyond the location pointed to by %rsp called red zone. Even if the function doesn't adjust rsp, its red zone is still protected.

Upvotes: 2

Related Questions