見出し画像

C言語コンパイラ依存した話の続編「gcc はどうコンパイルするのか」

こちらの記事の続きです。

もう、いつまでやってんのよっていう感じだけど。
ようやく、 gcc です。
そして、これが gcc が出力したアセンブラです。

_main:
LFB1445:
    .file 1 "../src/HelloWorld.cpp"
    .loc 1 15 0
    .cfi_startproc
    leal    4(%esp), %ecx
    .cfi_def_cfa 1, 0
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    .cfi_escape 0x10,0x5,0x2,0x75,0
    movl    %esp, %ebp
    pushl   %ecx
    .cfi_escape 0xf,0x3,0x75,0x7c,0x6
    subl    $36, %esp
    .loc 1 15 0
    call    ___main
LVL0:
    .loc 1 16 0
    // int i = 3, j = 5;
    movl    $3, -12(%ebp)		// i
    movl    $5, -16(%ebp)		// j

    // printf("MAX処理前 i = %d, j = %d\n", i, j);
    .loc 1 17 0
    movl    -16(%ebp), %eax		// eax = j
    movl    %eax, 8(%esp)		// 8(%esp) = j (printf の第3引数)
    movl    -12(%ebp), %eax		// eax = i
    movl    %eax, 4(%esp)		// 4(%esp) = i (printf の第2引数)
    movl    $LC0, (%esp)		// "MAX\345\207\246\347\220\206\345\211\215 i = %d, j = %d\12\0" (printf の第1引数)
    call    _printf

    // printf("MAX処理中 i = %d, j = %d, max = %d\n", i, j, MAX(++i,j--));
    .loc 1 18 0
    addl    $1, -12(%ebp)		// ++i
    movl    -16(%ebp), %eax		// eax = j
    leal    -1(%eax), %edx		// edx = j-1
    movl    %edx, -16(%ebp)		// j--
    cmpl    %eax, -12(%ebp)		// j : ++i
    jle L2

    // j < ++i
    .loc 1 18 0 is_stmt 0 discriminator 1
    addl    $1, -12(%ebp)		// ++(++i)
    movl    -12(%ebp), %eax		// eax = ++(++i)
    jmp L3

    // j >= ++i
L2:
    .loc 1 18 0 discriminator 2
    movl    -16(%ebp), %eax		// eax = j--
    leal    -1(%eax), %edx		// edx = (j--)--
    movl    %edx, -16(%ebp)		// (j--)--

L3:
    .loc 1 18 0 discriminator 4
    movl    %eax, 12(%esp)		// 12(%esp) = MAX(++i,j--) の結果
(printf の第4引数)
    movl    -16(%ebp), %eax		// eax = j
    movl    %eax, 8(%esp)		// 8(%esp) = j (printf の第3引数)
    movl    -12(%ebp), %eax		// eax = i
    movl    %eax, 4(%esp) 		// 4(%esp) = i (printf の第2引数)
    movl    $LC1, (%esp)		// "MAX\345\207\246\347\220\206\344\270\255 i = %d, j = %d, max = %d\12\0" (printf の第1引数)
    call    _printf

    // printf("MAX処理後 i = %d, j = %d\n", i, j);
    .loc 1 19 0 is_stmt 1 discriminator 4
    movl    -16(%ebp), %eax		// eax = j
    movl    %eax, 8(%esp) 		// 8(%esp) = j (printf の第3引数)
    movl    -12(%ebp), %eax		// eax = i
    movl    %eax, 4(%esp) 		// 4(%esp) = i (printf の第2引数)
    movl    $LC2, (%esp)		// "MAX\345\207\246\347\220\206\345\276\214 i = %d, j = %d\12\0" (printf の第1引数)
    call    _printf

    .loc 1 21 0 discriminator 4
    movl    $LC3, 4(%esp)
    movl    $__ZSt4cout, (%esp)
    call    __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
    movl    %eax, %ecx
    call    __ZNSolsEPFRSoS_E
    subl    $4, %esp
    .loc 1 22 0 discriminator 4
    movl    $0, %eax
    .loc 1 23 0 discriminator 4
    movl    -4(%ebp), %ecx
    .cfi_def_cfa 1, 0
    leave
    .cfi_restore 5
    leal    -4(%ecx), %esp
    .cfi_def_cfa 4, 4
    ret

感想

オペランド

オペランドは、左から右への代入なのか。

// int i = 3, j = 5;
movl    $3, -12(%ebp)		// i
movl    $5, -16(%ebp)		// j

それぞれは次のような処理になる。

3 → -12(%ebp) へ代入
5 → -16(%ebp) へ代入

オペランドが左から右へ代入するアセンブラなんて、初めて見たかも。
通しで読んでいると、右左がごちゃごちゃになってくる。右から左になれてるもんだから。
なんで左から右なんだろう。

呼出関数の引数のスタック確保タイミング

printf に渡す引数も、main 関数の先頭で確保してます。呼び出す関数の引数サイズを最初に計算することになるのか。 printf 3回呼び出している場合、毎回スタックポインタを足したり引いたりする必要はなくなるけど。このパターンも初めてだなぁ。

movl    %eax, 8(%esp) 		// 8(%esp) = j (printf の第3引数)

8(%esp) は、main 関数の最初の方の次のコードで確保済み。

subl    $36, %esp

呼出関数の引数の渡し方も三者三様だった。

clang ARM:レジスタ
VS:スタックにpush(関数を呼び出す毎にスタック確保)
gcc:スタックに設定(スタックはまとめて確保)


この記事が気に入ったらサポートをしてみませんか?