2017-02-27 2 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から)出力生成:私はprintfの文のコメントを外したときに、今

.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 

を、コードは次のようになります。

.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自体がそれを壊すからです。しかし、デイデイの最適化されていないコードは最適化されていないので、何かの中に何があるのか​​を尋ねる意味はありません – 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をインクリメントしてprintfを保護します。

実際、このような最適化コンパイラには多くのことがあります。たとえば、ABIでは、コールが呼び出される前にスタックが16バイトに整列されていなければならず、%rspが16バイトに整列するようにフレームが作成されることが指定されています。しかし、関数が他の関数を呼び出さない場合や、SSE命令(アライメントされたスタックフレームを必要とする命令の一例)を使用しない場合は、ABIの要件を破ります。 これは実際には可能なすべてのバイトを節約するために行われたマイクロ最適化です。

関連する問題