名前空間
変種
操作

未定義な振る舞い

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

言語の特定のルールが破られた場合、プログラム全体を無意味にします。

目次

[edit] 説明

C++ 標準は、以下のいずれかのクラスに該当しないすべての C++ プログラムの観測可能な動作を正確に定義します

  • 形式不適格 (ill-formed) - プログラムに構文エラーまたは診断可能な意味エラーがある場合。
  • 適合する C++ コンパイラは、そのようなコードに意味を割り当てる言語拡張を定義している場合でも (可変長配列の場合など)、診断を発行する必要があります。
  • 標準のテキストでは、これらの要件を示すために shallshall not、および ill-formed を使用します。
  • そのようなプログラムが実行された場合、動作は未定義になります。
  • 実装定義の動作 (implementation-defined behavior) - プログラムの動作は実装によって異なり、適合する実装は各動作の結果を文書化する必要があります。
  • 例としては、std::size_t の型やバイト内のビット数、std::bad_alloc::what のテキストなどがあります。
  • 実装定義の動作のサブセットは、実装によって提供されるロケールに依存するロケール固有の動作 (locale-specific behavior) です。
  • 不特定な動作 (unspecified behavior) - プログラムの動作は実装によって異なり、適合する実装は各動作の結果を文書化する必要はありません。
  • 例としては、評価順序、同一の文字列リテラルが異なるかどうか、配列割り当てのオーバーヘッド量などがあります。
  • 各不特定な動作は、有効な結果のセットのいずれかの結果になります。
  • 誤った動作 (erroneous behavior) - 実装が診断を推奨する (誤った) 動作。
  • 誤った動作は常に、誤ったプログラムコードの結果です。
  • 定数式の評価は、決して誤った動作を引き起こしません。
  • 実行に誤った動作が指定されている操作が含まれる場合、実装は診断を発行することが許可され、推奨され、その操作の後に不特定の時点で実行を終了することが許可されます。
  • 実装は、プログラムの動作に関する実装固有の一連の仮定の下で誤った動作に到達可能であると判断できる場合、診断を発行できます。これにより、誤検知が発生する可能性があります。
誤った動作の例
#include <cassert>
#include <cstring>
 
void f()
{   
    int d1, d2;       // d1, d2 have erroneous values
    int e1 = d1;      // erroneous behavior
    int e2 = d1;      // erroneous behavior
    assert(e1 == e2); // holds
    assert(e1 == d1); // holds, erroneous behavior
    assert(e2 == d1); // holds, erroneous behavior
 
    std::memcpy(&d2, &d1, sizeof(int)); // no erroneous behavior, but
                                        // d2 has an erroneous value
 
    assert(e1 == d2); // holds, erroneous behavior
    assert(e2 == d2); // holds, erroneous behavior
}
 
unsigned char g(bool b)
{
    unsigned char c;     // c has erroneous value
    unsigned char d = c; // no erroneous behavior, but d has an erroneous value
    assert(c == d);      // holds, both integral promotions have erroneous behavior
    int e = d;           // erroneous behavior
    return b ? d : 0;    // erroneous behavior if b is true
}
(C++26以降)
  • 未定義の動作 (undefined behavior) - プログラムの動作に制限はありません。
  • 未定義の動作の例としては、データ競合、配列境界外のメモリアクセス、符号付き整数オーバーフロー、ヌルポインタの逆参照、式内の同じスカラーに対する複数の変更中間シーケンスポイントなしに(C++11まで)シーケンスされていない(C++11から)異なる型のポインタを介したオブジェクトへのアクセスなどがあります。
  • 実装は未定義の動作を診断する必要はありません (多くの単純な状況は診断されますが)、コンパイルされたプログラムは意味のあることを何もする必要はありません。
  • 実行時未定義動作 (runtime-undefined behavior) - コア定数式としての式の評価中に発生する場合を除き、未定義の動作。
(C++11以降)

[edit] UB と最適化

正しい C++ プログラムは未定義の動作を含まないため、UB を持つプログラムを最適化を有効にしてコンパイルすると、コンパイラは予期しない結果を生成する可能性があります

例えば、

[edit] 符号付きオーバーフロー

int foo(int x)
{
    return x + 1 > x; // either true or UB due to signed overflow
}

(デモ) としてコンパイルされる可能性があります。

foo(int):
        mov     eax, 1
        ret

[edit] 範囲外アクセス

int table[4] = {};
bool exists_in_table(int v)
{
    // return true in one of the first 4 iterations or UB due to out-of-bounds access
    for (int i = 0; i <= 4; i++)
        if (table[i] == v)
            return true;
    return false;
}

(デモ) としてコンパイルされる可能性があります。

exists_in_table(int):
        mov     eax, 1
        ret

[edit] 未初期化のスカラー

std::size_t f(int x)
{
    std::size_t a;
    if (x) // either x nonzero or UB
        a = 42;
    return a;
}

(デモ) としてコンパイルされる可能性があります。

f(int):
        mov     eax, 42
        ret

表示された出力は、以前のバージョンの gcc で観測されました。

#include <cstdio>
 
int main()
{
    bool p; // uninitialized local variable
    if (p)  // UB access to uninitialized scalar
        std::puts("p is true");
    if (!p) // UB access to uninitialized scalar
        std::puts("p is false");
}

実行結果の例

p is true
p is false

[edit] 無効なスカラー

int f()
{
    bool b = true;
    unsigned char* p = reinterpret_cast<unsigned char*>(&b);
    *p = 10;
    // reading from b is now UB
    return b == 0;
}

(デモ) としてコンパイルされる可能性があります。

f():
        mov     eax, 11
        ret

[edit] ヌルポインタの逆参照

この例は、ヌルポインタの逆参照の結果からの読み取りを示しています。

int foo(int* p)
{
    int x = *p;
    if (!p)
        return x; // Either UB above or this branch is never taken
    else
        return 0;
}
 
int bar()
{
    int* p = nullptr;
    return *p; // Unconditional UB
}

(デモ) としてコンパイルされる可能性があります。

foo(int*):
        xor     eax, eax
        ret
bar():
        ret

[edit] std::realloc に渡されたポインタへのアクセス

表示された出力を確認するには、clang を選択してください。

#include <cstdlib>
#include <iostream>
 
int main()
{
    int* p = (int*)std::malloc(sizeof(int));
    int* q = (int*)std::realloc(p, sizeof(int));
    *p = 1; // UB access to a pointer that was passed to realloc
    *q = 2;
    if (p == q) // UB access to a pointer that was passed to realloc
        std::cout << *p << *q << '\n';
}

実行結果の例

12

[edit] 副作用のない無限ループ

表示された出力を確認するには、clang または最新の gcc を選択してください。

#include <iostream>
 
bool fermat()
{
    const int max_value = 1000;
 
    // Non-trivial infinite loop with no side effects is UB
    for (int a = 1, b = 1, c = 1; true; )
    {
        if (((a * a * a) == ((b * b * b) + (c * c * c))))
            return true; // disproved :()
        a++;
        if (a > max_value)
        {
            a = 1;
            b++;
        }
        if (b > max_value)
        {
            b = 1;
            c++;
        }
        if (c > max_value)
            c = 1;
    }
 
    return false; // not disproved
}
 
int main()
{
    std::cout << "Fermat's Last Theorem ";
    fermat()
        ? std::cout << "has been disproved!\n"
        : std::cout << "has not been disproved.\n";
}

実行結果の例

Fermat's Last Theorem has been disproved!

[edit] 診断メッセージ付きの形式不適格

コンパイラは、形式不適格なプログラムに意味を与える方法で言語を拡張することが許可されていることに注意してください。そのような場合、C++ 標準が要求するのは診断メッセージ (コンパイラの警告) のみです。ただし、プログラムが「診断不要の形式不適格」である場合を除きます。

たとえば、--pedantic-errors を介して言語拡張が無効にされていない限り、GCC は次の例を警告のみでコンパイルします。これは、それが「エラー」の例としてC++ 標準に記載されているにもかかわらずです (「GCC Bugzilla #55783」も参照)。

#include <iostream>
 
// Example tweak, do not use constant
double a{1.0};
 
// C++23 standard, §9.4.5 List-initialization [dcl.init.list], Example #6:
struct S
{
    // no initializer-list constructors
    S(int, double, double); // #1
    S();                    // #2
    // ...
};
 
S s1 = {1, 2, 3.0}; // OK, invoke #1
S s2{a, 2, 3}; // error: narrowing
S s3{}; // OK, invoke #2
// — end example]
 
S::S(int, double, double) {}
S::S() {}
 
int main()
{
    std::cout << "All checks have passed.\n";
}

実行結果の例

main.cpp:17:6: error: type 'double' cannot be narrowed to 'int' in initializer ⮠
list [-Wc++11-narrowing]
S s2{a, 2, 3}; // error: narrowing
     ^
main.cpp:17:6: note: insert an explicit cast to silence this issue
S s2{a, 2, 3}; // error: narrowing
     ^
     static_cast<int>( )
1 error generated.

[edit] 参考文献

拡張コンテンツ
  • C++23標準 (ISO/IEC 14882:2024)
  • 3.25 ill-formed program [defns.ill.formed]
  • 3.26 implementation-defined behavior [defns.impl.defined]
  • 3.66 unspecified behavior [defns.unspecified]
  • 3.68 well-formed program [defns.well.formed]
  • C++20 standard (ISO/IEC 14882:2020)
  • TBD ill-formed program [defns.ill.formed]
  • TBD implementation-defined behavior [defns.impl.defined]
  • TBD unspecified behavior [defns.unspecified]
  • TBD well-formed program [defns.well.formed]
  • C++17 standard (ISO/IEC 14882:2017)
  • TBD ill-formed program [defns.ill.formed]
  • TBD implementation-defined behavior [defns.impl.defined]
  • TBD unspecified behavior [defns.unspecified]
  • TBD well-formed program [defns.well.formed]
  • C++14 standard (ISO/IEC 14882:2014)
  • TBD ill-formed program [defns.ill.formed]
  • TBD implementation-defined behavior [defns.impl.defined]
  • TBD unspecified behavior [defns.unspecified]
  • TBD well-formed program [defns.well.formed]
  • C++11 standard (ISO/IEC 14882:2011)
  • TBD ill-formed program [defns.ill.formed]
  • TBD implementation-defined behavior [defns.impl.defined]
  • TBD unspecified behavior [defns.unspecified]
  • TBD well-formed program [defns.well.formed]
  • C++98 標準 (ISO/IEC 14882:1998)
  • TBD ill-formed program [defns.ill.formed]
  • TBD implementation-defined behavior [defns.impl.defined]
  • TBD unspecified behavior [defns.unspecified]
  • TBD well-formed program [defns.well.formed]

[edit] 関連項目

[[assume(expression)]]
(C++23)
特定の時点でexpressionが常にtrueに評価されることを指定します
(属性指定子)[編集]
(C++26)
オブジェクトが初期化されていない場合、不定値を持つことを指定します。
(属性指定子)[編集]
実行が到達不能な地点をマークする
(関数) [編集]
C ドキュメント 未定義の動作

[edit] 外部リンク

1.  The LLVM Project Blog: C プログラマーが未定義の動作について知っておくべきこと #1/3
2.  The LLVM Project Blog: C プログラマーが未定義の動作について知っておくべきこと #2/3
3.  The LLVM Project Blog: C プログラマーが未定義の動作について知っておくべきこと #3/3
4.  未定義の動作はタイムトラベルを引き起こす可能性がある (他のこともそうだが、タイムトラベルが最も奇妙だ)
5.  C/C++ における整数オーバーフローの理解
6.  ヌルポインタの楽しみ、パート 1 (ヌルポインタの逆参照による UB が原因で発生した Linux 2.6.30 のローカルエクスプロイト)
7.  未定義の動作とフェルマーの最終定理
8.  C++ プログラマーのための未定義の動作ガイド
English 日本語 中文(简体) 中文(繁體)