12

次のCプログラムがあります。

int main()
{
    int c[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2};
    return c[0];
}

そして、gcc で -S ディレクティブを使用してコンパイルすると、次のアセンブリが得られます。

    .file   "array.c"
    .text
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $0, -48(%rbp)
    movl    $0, -44(%rbp)
    movl    $0, -40(%rbp)
    movl    $0, -36(%rbp)
    movl    $0, -32(%rbp)
    movl    $0, -28(%rbp)
    movl    $0, -24(%rbp)
    movl    $0, -20(%rbp)
    movl    $1, -16(%rbp)
    movl    $2, -12(%rbp)
    movl    -48(%rbp), %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.4.5 20110214 (Red Hat 4.4.5-6)"
    .section        .note.GNU-stack,"",@progbits

私が理解していないのは、以前の配列要素がbpから離れているのはなぜですか? 配列の要素が逆の順序で配置されているようです。

また、配列要素をスタックにプッシュするために、gcc が movl の代わりに push を使用しないのはなぜですか?


別の見方

私が得るモジュールへの静的変数として配列をグローバル名前空間に移動します:

    .file   "array.c"
    .data
    .align 32
    .type   c, @object
    .size   c, 40
c:
    .long   0
    .long   0
    .long   0
    .long   0
    .long   0
    .long   0
    .long   0
    .long   0
    .long   1
    .long   2
    .text
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    c(%rip), %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.4.5 20110214 (Red Hat 4.4.5-6)"
    .section    .note.GNU-stack,"",@progbits

次の C プログラムを使用します。

static int c[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2};

int main() 
{
    return c[0];
}

これはスタックへのより多くの洞察を与えません。しかし、わずかに異なるセマンティクスを使用したアセンブリの差分出力を見るのは興味深いことです。

4

3 に答える 3

9

まず、x86 スタックは下向きに成長します。慣例によりrbp、 の元の値を格納しますrsp。したがって、関数の引数はに対して正のオフセットに存在しrbp、その自動変数はのオフセットに存在します。自動配列の最初の要素は、他のすべての要素よりもアドレスが小さいため、 から最も離れていrbpます。

このページに表示される便利な図を次に示します。

スタック レイアウト

コンパイラが一連の命令を使用して配列を初期化できなかった理由がわかりません。pushこれが良いアイデアかどうかはわかりません。

于 2011-12-12T17:52:09.217 に答える
3

また、配列要素をスタックにプッシュするために、gcc が movl の代わりに push を使用しないのはなぜですか?

一連のプッシュを使用できるスタック フレームの正確な場所に初期化された大きな配列があることは非常にまれであるため、gcc はそのようにするように教えられていません。(詳細: 配列の初期化は、ブロック メモリのコピーとして処理されます。これは、サイズmemcpyに応じて、移動命令のシーケンスまたは への呼び出しのいずれかとして発行されます。何を発行するかを決定するコードは、どこに発行するかを知りません。メモリ内でブロックが実行されるため、代わりに使用できるかどうかはわかりませんpush。)

また、movlより高速です。具体的にpushは、 の暗黙的な読み取り-変更-書き込みを行う%espため、一連のpushes を順番に実行する必要があります。 movl対照的に、独立したアドレスへのアクセスは並列で実行できます。movlそのため、esではなく s のシーケンスを使用することでpush、gcc は CPU に、より多くの命令レベルの並列処理を提供します。

任意のレベルの最適化を有効にしてコードをコンパイルすると、配列が完全に消失することに注意してください。-O1これが (これは出力objdump -drではなく、オブジェクト ファイルで実行した結果な-Sので、実際のマシン コードを確認できます)

0000000000000000 <main>:
   0:   b8 00 00 00 00          mov    $0x0,%eax
   5:   c3                      retq   

-Os:

0000000000000000 <main>:
   0:   31 c0                   xor    %eax,%eax
   2:   c3                      retq   

何もしないことは、何かをするよりも常に速いです。レジスターのクリアxorは 5 バイトではなく 2 バイトですが、正式なデータはレジスターの古い内容に依存し、条件コードを変更するため、速度が遅くなる可能性があるため、サイズを最適化する場合にのみ選択されます。

于 2011-12-12T18:19:36.010 に答える
2

x86 では、スタックが下向きに成長することに注意してください。スタックにプッシュすると、スタック ポインターから減算されます。

%rbp <-- Highest memory address
-12
-16
-20
-24
-28
-32
-36
-40
-44
-48  <-- Address of array
于 2011-12-12T17:51:57.847 に答える