名前空間
変種
操作

as-if ルール

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

プログラムの観察可能な動作を変更しないあらゆるコードの変更が許されます。

目次

[編集] 説明

C++ コンパイラは、以下の内容を満たす限り、プログラムにいかなる変更を加えることも認められています。

1) すべての副作用完了点において、すべての volatile オブジェクトの値が安定である (以前の評価が完了し、新しい評価が開始していない)。
(C++11未満)
1) volatile オブジェクトへのアクセス (読み書き) は、それらが発生する式の意味論に厳密に従って発生する。 特に、それらは同じスレッド上の他の volatile アクセスに関して並べ替えられません。
(C++11以上)
2) プログラムの終了時、ファイルに書き込まれたデータは、そのプログラムが書かれた通りに実行されたかのように、正確に存在する。
3) 対話デバイスに送られたテキストは、プログラムが入力を待つ前に表示される。
4) ISO C のプラグマ #pragma STDC FENV_ACCESS がサポートされていて、 ON に設定されている場合、浮動小数点環境 (浮動小数点例外および丸めモード) への変更は、書かれた通りに実行されたかのように、浮動小数点算術演算子および関数呼び出しによって観察されることが保証される。 ただし、
  • キャストおよび代入以外のあらゆる浮動小数点式の結果は、その式の型とは異なる浮動小数点型の範囲および精度を持っていても構いません (FLT_EVAL_METHOD を参照してください)。
  • 上記とは別に、あらゆる浮動小数点式の中間結果は、無限の範囲と精度で行われたかのように計算されても構いません ( #pragma STDC FP_CONTRACTOFF でない場合)。

[編集] ノート

コンパイラは、 (通常は) 入出力や volatile アクセスを行うかどうかを判定するために外部ライブラリのコードを解析することはできないため、サードパーティライブラリの呼び出しは最適化によって影響を受けません。 しかし、標準ライブラリの呼び出しは、最適化によって、他の呼び出しに置き換えられたり、省略されたり、またはプログラムに追加されることがあります。 静的にリンクされたサードパーティライブラリのコードはリンク時最適化の対象となることがあります。

未定義動作 (例えば配列の範囲外へのアクセス、 const オブジェクトの変更、評価順序の違反など) を含むプログラムは、 as-if ルールに縛られません。 それらは、異なる最適化設定で再コンパイルしたとき、しばしば観察可能な動作が変更されます。 例えば、符号付き整数のオーバーフローに対する確認がそのオーバーフローの結果に依存している場合 (例えば if(n+1 < n) abort();)、符号付きのオーバーフローは未定義動作であるため、それは決して発生せず確認は不要であると仮定して良いので、コンパイラによってはそれを完全に除去することがあります

コピー省略は as-if ルールの例外です。 コンパイラは、たとえ観察可能な副作用が含まれていても、一時オブジェクトのムーブコンストラクタおよびコピーコンストラクタの呼び出しや、それに対応するデストラクタの呼び出しを除去しても構いません。

new 式には 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; と同等の内容に縮小されています。

[編集] 関連項目