Namespaces
Variants

The as-if rule

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

目次

説明

観測可能な動作 プログラムの観測可能な動作には以下が含まれます:

  • すべての シーケンスポイント において、すべての volatile オブジェクトの値は安定している(以前の評価は完了し、新しい評価は開始されていない)。
(C++11まで)
  • volatile オブジェクトへのアクセス(読み書き)は、それが現れる式のセマンティクスに厳密に従って発生する。特に、同じスレッド上の他のvolatileアクセスに対して リオーダーされない
(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まで)

プログラムは 観測可能なチェックポイント を含むことがあります。

操作 OP は、すべての未定義操作 U に対して、観測可能なチェックポイント CP が存在し、 OP CP よりも前に発生し、 CP U よりも前に発生する場合、 未定義フリー です。与えられた入力に対するプログラムの 定義済みプレフィックス は、すべての未定義フリー操作で構成されます。

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