以下のコードをそれぞれアセンブリ言語へコンパイルします。
Cソース
同条件下で比較するため為、両者の書き方を出来るだけ近づけました。
//#include<stdio.h> /* debug */
int main() {
int a=0;
for(int i=0;i<7;i++){
a+=i;
}
//printf("result = %d",a); /* debug */
return 0;
}
//#include<stdio.h> /* debug */
int main() {
int a=0;
int i=0;
while(i<7){
a+=i;
i++;
}
//printf("result = %d",a); /* debug */
return 0;
}
コンパイル
C:\Users\nanasi\loopasm>gcc -S -masm=intel -o forasm.s forasm.c
C:\Users\nanasi\loopasm>gcc -S -masm=intel -o whileasm.s whileasm.c
.file "forasm.c"
.intel_syntax noprefix
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
push rbp
.seh_pushreg rbp
mov rbp, rsp
.seh_setframe rbp, 0
sub rsp, 48
.seh_stackalloc 48
.seh_endprologue
call __main
mov DWORD PTR -4[rbp], 0
mov DWORD PTR -8[rbp], 0
jmp .L2
.L3:
mov eax, DWORD PTR -8[rbp]
add DWORD PTR -4[rbp], eax
add DWORD PTR -8[rbp], 1
.L2:
cmp DWORD PTR -8[rbp], 6
jle .L3
mov eax, 0
add rsp, 48
pop rbp
ret
.seh_endproc
.def __main; .scl 2; .type 32; .endef
.ident "GCC: (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r3) 14.2.0"
.file "whileasm.c"
.intel_syntax noprefix
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
push rbp
.seh_pushreg rbp
mov rbp, rsp
.seh_setframe rbp, 0
sub rsp, 48
.seh_stackalloc 48
.seh_endprologue
call __main
mov DWORD PTR -4[rbp], 0
mov DWORD PTR -8[rbp], 0
jmp .L2
.L3:
mov eax, DWORD PTR -8[rbp]
add DWORD PTR -4[rbp], eax
add DWORD PTR -8[rbp], 1
.L2:
cmp DWORD PTR -8[rbp], 6
jle .L3
mov eax, 0
add rsp, 48
pop rbp
ret
.seh_endproc
.def __main; .scl 2; .type 32; .endef
.ident "GCC: (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r3) 14.2.0"
比較
1行づつ確認するのは難しい為winmergiを用いて比較します。
なんということでしょう。ソース名以外は完全に一致しました。
実際forとwhileを使う時にはプログラムの書き方も異なってくると思いますが、forとwhile自体にはあまり差が無いようです。
分析
せっかくアセンブリ言語へ変換したので中身を読んでみます。
;...前略
mov DWORD PTR -4[rbp], 0 ; int a=0 (変数aはrbp-4に格納)
mov DWORD PTR -8[rbp], 0 ; int i=0 (変数iはrbp-8に格納)
jmp .L2 ; 先ずL2へ跳びcmp命令でi<7の条件に当てはまるかを確認する
.L3: ;L3ラベルの処理がfor文の中身に相当
mov eax, DWORD PTR -8[rbp] ; eax = i
add DWORD PTR -4[rbp], eax ; a = a + eax
add DWORD PTR -8[rbp], 1 ; i++
.L2:
cmp DWORD PTR -8[rbp], 6 ; i==6 ならば L3へ跳ぶ(iがintの場合 i<7 は i<=6 と等価)
jle .L3
mov eax, 0 ; return 0(main関数の戻り値はeax経由で返す)
;後略...
以下の流れになっていることが分かりました。
スタックへ変数a,iを格納
ラベルL2にてi==6の判定を行う
i<=6の場合ラベルL3の中でa+=i及びi++を実行
iが7になったら終了
eaxにmain関数の返り値0を格納
eaxが本当に返り値を表していることの根拠
https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170
Return values
A scalar return value that can fit into 64 bits, including the __m64 type, is returned through RAX. Nonscalar types including floats, doubles, and vector types such as __m128, __m128i, __m128d are returned in XMM0. The state of unused bits in the value returned in RAX or XMM0 is undefined.
(機械翻訳:64ビットに収まるスカラー戻り値(__m64型を含む)はRAXレジスタを通じて返されます。浮動小数点数(float/double)やベクトル型(__m128、__m128i、__m128dなど)を含む非スカラー型はXMM0レジスタに返されます。RAXまたはXMM0で返される値のうち、使用されなかったビットの状態は未定義です。)
eaxとraxはビット数が違うだけで同じレジスタです。(よね?)
以上