gccのアセンブラ出力を見ていて気になったのでメモ。
C言語ではif文を使わなくても、3項演算子を使用して条件分岐を “( a > b ) ? a : b”のような感じで書く事が出来ますが、両者の書き方で生成されるコードが異なるようでした。
3項演算子を使用した場合(test01.c)
#include <stdio.h> int sum( int a, int b ); int main() { int a = 10; int b = 10; int result; result = ( a > b ) ? a : b; // printf( "largest value is %d\n", result ); return 0; } |
普通にifを使用した場合(test02.c)
#include <stdio.h> int sum( int a, int b ); int main() { int a = 10; int b = 10; int result; if ( a > b ) { result = a; } else { result = b; } // printf( "largest value is %d\n", result ); return 0; } |
それぞれ、以下のコマンドでアセンブラ出力を行います
gcc -S test01.c gcc -S test02.c |
出力結果を確認します。
3項演算子を使用した場合
.file "test01.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $16, %esp movl $10, -12(%ebp) movl $10, -8(%ebp) movl -12(%ebp), %eax cmpl %eax, -8(%ebp) cmovge -8(%ebp), %eax movl %eax, -4(%ebp) movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1" .section .note.GNU-stack,"",@progbits |
ifの場合
.file "test02.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 subl $16, %esp movl $10, -12(%ebp) movl $10, -8(%ebp) movl -12(%ebp), %eax cmpl -8(%ebp), %eax jle .L2 movl -12(%ebp), %eax movl %eax, -4(%ebp) jmp .L3 .L2: movl -8(%ebp), %eax movl %eax, -4(%ebp) .L3: movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1" .section .note.GNU-stack,"",@progbits |
両者とも2つの変数値に、movl命令でアクセスし、cmplで比較しています。
movl $10, -12(%ebp) movl $10, -8(%ebp) movl -12(%ebp), %eax cmpl -8(%ebp), %eax |
その後、3項演算子はcmovgeという命令で処理しているのに対して…
cmovge -8(%ebp), %eax movl %eax, -4(%ebp) movl $0, %eax |
ifではジャンプ命令で普通に分岐させています
jle .L2 movl -12(%ebp), %eax movl %eax, -4(%ebp) jmp .L3 .L2: movl -8(%ebp), %eax movl %eax, -4(%ebp) .L3: movl $0, %eax leave |
cmovge命令が何かと思って調べてみたら、msdnから確認できました。
説明によると、条件付での代入な様です。
http://msdn.microsoft.com/en-us/library/aa226718%28v=vs.60%29.aspx
Syntax cmovge $s_reg1, $s_reg2, $d_reg cmovge $d_reg/$s_reg1, $s_reg2 cmovge $s_reg1, val_immed, $d_reg cmovge $d_reg/$s_reg1, val_immed Description Move if Greater Than or Equal to Zero moves the contents of $s_reg2 or the immediate value to the destination register if the contents of $s_reg1 is greater than or equal to zero. |
もちろんintelのドキュメントにも記載されています。
http://download.intel.com/jp/developer/jpdoc/EM64T_VOL1_30083402_i.pdf
CMOVcc - Conditional Move命令 ----------------------------- IA-32e モードでの操作 temp <-- DEST IF condition TRUE THEN DEST <-- SRC ELSIF (osize = 32) DEST <-- temp AND 0x00000000FFFFFFFF FI; ELSE DEST <-- temp FI; FI; |
こっちのほうが、プログラムっぽく記述されているので分かりやすい。
まさに条件付の代入でした。
速度的には、おそらく3項演算子版のほうが良いだろうと思ってベンチマークを取ってみたのですが、なぜかifバージョンのほうが若干速い結果でした。
なぜだろう….
ちなみに当たり前の事ですが、生成されるコードはコンパイラやターゲットCPUに依存するので、”両者で必ず異なるコードが生成される”という意味ではないです。
関連記事
コメントを残す