The as-if rule
プログラムの観測可能な動作を変更しないあらゆるコード変換を許可します。
目次 |
説明
観測可能な動作 プログラムの観測可能な動作には以下が含まれます:
| (C++11まで) | |
| (C++11以降) |
|
(C++26まで) |
|
(C++26以降) |
- 対話デバイスに送信されるプロンプトテキストは、プログラムが入力待ちになる前に表示されます。
-
ISO Cプラグマ
#pragma STDC FENV_ACCESS
がサポートされており、かつ
ONに設定されている場合、 浮動小数点環境 (浮動小数点例外と丸めモード)への変更は、浮動小数点算術演算子と関数呼び出しによって、記述された通りに実行されたかのように観測されることが保証されます。ただし以下の例外があります:- キャストと代入以外の浮動小数点式の結果は、式の型とは異なる浮動小数点型の範囲と精度を持つ可能性があります( FLT_EVAL_METHOD を参照)。
-
上記にかかわらず、あらゆる浮動小数点式の中間結果は、無限の範囲と精度で計算される可能性があります(
#pragma STDC FP_CONTRACT
が
OFFでない限り)。
|
C++コンパイラは、同じ入力が与えられた場合に、プログラムの観測可能な動作がその入力に対応する可能な観測可能な動作の1つである限り、プログラムに対して任意の変更を実行することが許可されています。 ただし、特定の入力が 未定義動作 をもたらす場合、コンパイラはその入力によるプログラムのいかなる観測可能な動作も保証できません。たとえ観測可能な動作のいずれかの操作が、いかなる可能な未定義操作よりも前に発生する場合でも同様です。 |
(C++26まで) |
|
プログラムは 観測可能なチェックポイント を含むことがあります。
操作
C++コンパイラは、同じ入力が与えられた場合に、プログラムの定義済みプレフィックスの観測可能な動作が、その定義済みプレフィックスに対応する可能な観測可能な動作の1つである限り、プログラムに対して任意の変更を実行することが許可されています。 特定の入力が 未定義動作 をもたらす場合、コンパイラは定義済みプレフィックスに属さないその入力によるプログラムのいかなる観測可能な動作も保証できません。 |
(C++26以降) |
注記
コンパイラは通常、外部ライブラリのコードを分析してI/Oまたはvolatileアクセスを実行するかどうかを判断できないため、サードパーティライブラリの呼び出しも最適化の影響を受けません。ただし、標準ライブラリの呼び出しは、最適化中に他の呼び出しに置き換えられたり、削除されたり、プログラムに追加されたりする可能性があります。静的にリンクされたサードパーティライブラリコードは、リンク時最適化の対象となる場合があります。
未定義動作を含むプログラムは、異なる最適化設定で再コンパイルすると、しばしば観測可能な動作が変化します。例えば、符号付き整数オーバーフローの検査がそのオーバーフローの結果に依存している場合、 if ( n + 1 < n ) abort ( ) ; のように記述されていると、 一部のコンパイラでは完全に除去されます 。これは 符号付きオーバーフローは未定義動作である ため、オプティマイザはそれが決して発生しないと仮定して自由に最適化でき、この検査が冗長と判断されるからです。
コピー省略 はas-ifルールからの例外です:コンパイラは、移動コンストラクタおよびコピーコンストラクタの呼び出しと、一時オブジェクトのデストラクタに対する対応する呼び出しを、それらの呼び出しが観測可能な副作用を持つ場合でも除去することができます。
|
new expression には、as-ifルールからの別の例外があります:コンパイラは、 置換可能なアロケーションファンクション の呼び出しを、ユーザー定義の置換が提供されていて観測可能な副作用がある場合でも除去することができます。 |
(C++14以降) |
浮動小数点例外の数と順序は、次の浮動小数点演算で観測される状態が最適化が行われなかったかのようである限り、最適化によって変更される可能性があります:
#pragma STDC FENV_ACCESS ON for (i = 0; i < n; ++i) x + 1; // x + 1 はデッドコードですが、FP例外を発生させる可能性があります // (オプティマイザが証明できない限り)。しかし、これをn回実行すると // 同じ例外が繰り返し発生します。したがって、以下のように最適化できます: if (0 < n) x + 1;
例
int& preinc(int& n) { return ++n; } int add(int n, int m) { return n + m; } // 定数畳み込みを防ぐためのvolatile入力 volatile int input = 7; // 結果を可視的な副作用として出力するためのvolatile出力 volatile int result; int main() { int n = input; // 組み込み演算子を使用すると未定義動作を引き起こす // int m = ++n + ++n; // しかし関数を使用することで、関数がオーバーラップしていないかのように // コードが実行されることを保証する int m = add(preinc(n), preinc(n)); result = m; }
出力:
# GCCコンパイラによって生成されたmain()関数の完全なコード
# x86 (Intel) プラットフォーム:
movl input(%rip), %eax # eax = input
leal 3(%rax,%rax), %eax # eax = 3 + eax + eax
movl %eax, result(%rip) # result = eax
xorl %eax, %eax # eax = 0 (main()の戻り値)
ret
# PowerPC (IBM) プラットフォーム:
lwz 9,LC..1(2)
li 3,0 # r3 = 0 (main()の戻り値)
lwz 11,0(9) # r11 = input;
slwi 11,11,1 # r11 = r11 << 1;
addi 0,11,3 # r0 = r11 + 3;
stw 0,4(9) # result = r0;
blr
# Sparc (Sun) プラットフォーム:
sethi %hi(result), %g2
sethi %hi(input), %g1
mov 0, %o0 # o0 = 0 (main()の戻り値)
ld [%g1+%lo(input)], %g1 # g1 = input
add %g1, %g1, %g1 # g1 = g1 + g1
add %g1, 3, %g1 # g1 = 3 + g1
st %g1, [%g2+%lo(result)] # result = g1
jmp %o7+8
nop
# すべてのケースにおいて、preinc()の副作用は排除され、
# main()関数全体は result = 2 * input + 3; と同等に縮約された
関連項目
|
C documentation
for
as-if rule
|