2017-02-27 54 views
1

我不知道我的標題是否合適,導致我的問題是:我知道有時候(當我想使用argv []時,編譯器必須安排空間命令行參數的堆棧。現在我編寫了一個簡單的程序來看看C編譯器如何處理簡單的C數組(實際上它的處理方式與std::array相同)。我正在使用Manjaro Linux 64位。 C代碼看起來是這樣的:爲什麼編譯器在堆棧上創建空間

#include <stdio.h> 

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

的組件產生的輸出(來自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 

現在,當我取消printf聲明,代碼如下所示:

.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 

中間部分很明顯,只叫printf。但爲什麼編譯器在這裏放置了一條subq $32, %rsp行?爲什麼不出現在第一個例子中,沒有printf聲明?

+4

我不認爲打開優化檢查生成的代碼是沒有任何價值的。 – DeiDei

+0

可能$ 32意味着ASCII'2',它只是硬編碼這個值而不是計算它?你已經啓用了哪些優化? – Lundin

+1

「爲什麼不出現在第一個示例中,沒有printf語句?」它不必,因爲它是(堆棧式)最深的功能。調用printf的版本必須保存堆棧,因爲printf本身會禁止它。但DeiDei的未經優化的代碼沒有經過優化,在 – Tommylee2k

回答

3

如果您想查看生成的.s彙編程序文件,您應該使用gcc -S -fverbose-asm -O編譯您的(真實)代碼。

請注意,最近的ABIcalling conventions要求堆棧指針至少要16字節對齊(特別是爲了兼容AVX或SSE)。還請閱讀Red Zone(建議by Zang Ming Jie)。

但爲什麼編譯器會在這裏放置一條subq $32, %rsp行?爲什麼不出現在第一個例子中,沒有printf聲明?

可能因爲沒有任何電話給printf您的main已成爲葉子程序。 因此,編譯器不需要更新%rsp以符合ABI(在中調用printf調用幀)。

+0

輸出幾乎相同,只有幾條註釋出現在行尾 – Frynio

2

rsp用來傳遞幀指向內部調用,如果一個函數沒有調用其他函數,它不需要調整rsp偏移量。

注意:%rsp指向的位置之外有一個128字節的區域,稱爲紅色區域。即使該功能沒有調整rsp,其紅色區域仍然受到保護。

+0

好的,但後來我只需要2個printf參數的空間,所以這將是2x8字節= 16 – Frynio

+1

這是非常不準確的。 rsp是堆棧指針,用於保留內存。可能是在OP的環境中,你可以在不降低rsp的情況下寫入堆棧,但在其他環境中,這會具有破壞性。 – linuxfan

+0

@Frynio數組本身5 * 4 = 20B也存在,它是局部變量,所以它駐留在堆棧內存中。從'main'返回時,堆棧幀被拋棄,因此任何本地POD變量都會自動「釋放」(儘管對象需要先析構函數調用,因此在釋放堆棧幀和「ret」之前會有更多的彙編代碼)。實際上,printf不會爲參數提供空間,您正在使用64b ABI,其中參數在寄存器中發送(如果可能,請在此處)。 – Ped7g

4

我爲每個程序集添加了註釋。

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 
4

這是您的編譯器所做的優化。它在第一種情況下實現了main是一個葉函數,因此它知道該數組在堆棧上是安全的。而在第二種情況下,對printf的調用會覆蓋堆棧幀,因此它通過增加%rsp來保護它。

其實編譯器有很多這樣的優化。例如,ABI指定堆棧必須在調用之前對齊16個字節,並且以%rsp爲16個字節對齊的方式創建幀。但是,如果該函數不調用任何其他函數或不使用任何SSE指令(需要對齊堆棧幀的指令的一個示例),則會破壞ABI要求。 這些實際上只是微型優化,以節省每個可能的字節。