[gcc]if文と3項演算子では,出力されるコードが異なる

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に依存するので、”両者で必ず異なるコードが生成される”という意味ではないです。

関連記事

コメントを残す

メールアドレスが公開されることはありません。