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:スタックに設定(スタックはまとめて確保)