条件付き移動(アセンブリcmov
)を使用してCの条件式?:
を最適化することは一般的な最適化です。ただし、C標準では次のように記述されています。
最初のオペランドが評価されます。評価と第2または第3オペランド(評価される方)の評価の間にシーケンスポイントがあります。 2番目のオペランドは、最初のオペランドが0と等しくない場合にのみ評価されます。 3番目のオペランドは、最初のオペランドが0と等しい場合にのみ評価されます。結果は、2番目または3番目のオペランド(評価される方)の値であり、以下で説明する型に変換されます110)
たとえば、次のCコード
#include <stdio.h>
int main() {
int a, b;
scanf("%d %d", &a, &b);
int c= a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
次のように、最適化された関連asmコードを生成します。
call __isoc99_scanf
movl (%rsp), %esi
movl 4(%rsp), %ecx
movl $1, %edi
leal 2(%rcx), %eax
leal 1(%rsi), %edx
cmpl %ecx, %esi
movl $.LC1, %esi
cmovle %eax, %edx
xorl %eax, %eax
call __printf_chk
標準によると、条件式では1つのブランチのみが評価されます。しかし、ここでは両方のブランチが評価されており、これは標準のセマンティクスに反しています。この最適化はC標準に対してですか?または、多くのコンパイラー最適化には、言語標準と矛盾するものがありますか?
"as-ifルール" 、つまり C11 5.1.2.3p6 のため、最適化は正当です。
準拠する実装は、実行時に抽象セマンティクスを使用したプログラムの実行としてsameの観察可能な動作を生成するプログラムを生成するためにのみ必要ですwould have。標準の残りの部分では、これらを単に説明しています抽象セマンティクス。
コンパイルされたプログラムが内部で行うことはまったく問題ではありません。唯一重要なことは、プログラムが終了したときに、a
およびb
を読み取って印刷することを除いて、他の観察可能な動作がないことですa + 1
またはb + 2
の値は、a
またはb
のどちらが大きいかに応じて、動作が未定義になる原因が発生しない限り。 (不正な入力により、a、bは初期化されず、したがって未定義にアクセスします。範囲エラーおよび符号付きオーバーフローも発生する可能性があります。)未定義の動作が発生すると、すべてのベットがオフになります。
揮発性変数へのアクセスは抽象セマンティクスに従って厳密に評価する必要があるため、ここでvolatile
を使用して条件付き移動を取り除くことができます。
#include <stdio.h>
int main() {
volatile int a, b;
scanf("%d %d", &a, &b);
int c = a > b ? a + 1 : 2 + b;
printf("%d", c);
return 0;
}
にコンパイルする
call __isoc99_scanf@PLT
movl (%rsp), %edx
movl 4(%rsp), %eax
cmpl %eax, %edx
jg .L7
movl 4(%rsp), %edx
addl $2, %edx
.L3:
leaq .LC1(%rip), %rsi
xorl %eax, %eax
movl $1, %edi
call __printf_chk@PLT
[...]
.L7:
.cfi_restore_state
movl (%rsp), %edx
addl $1, %edx
jmp .L3
私のGCC Ubuntu 7.2.0-8ubuntu3.2によって
C標準では、Cコードを実行する抽象マシンについて説明しています。コンパイラは、その抽象化に違反しない限り、つまり適合プログラムが違いを認識できない限り、最適化を自由に実行できます。