Namespaces
Variants

Undefined behavior

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

言語の特定の規則に違反した場合、プログラム全体を無意味なものとしてレンダリングします。

目次

説明

C++標準は、以下の分類に該当しないすべてのC++プログラムの 観測可能な動作 を厳密に定義しています:

  • ill-formed - プログラムに構文エラーまたは診断可能な意味論的エラーがあります。
  • 準拠したC++コンパイラは、そのようなコードに意味を割り当てる言語拡張(可変長配列など)を定義している場合でも、診断メッセージを出力することが要求されます。
  • 標準規格の本文では、 shall shall not 、および ill-formed という用語を使用してこれらの要件を示しています。
  • ill-formed、 no diagnostic required - プログラムは意味論的な誤りを含み、一般的なケースでは診断不可能な場合がある(例: ODR 違反やリンク時にのみ検出可能なその他のエラー)。
  • そのようなプログラムが実行された場合、動作は未定義です。
  • implementation-defined behavior - プログラムの動作は実装によって異なり、適合実装は各動作の影響を文書化しなければならない。
  • 例えば、 std::size_t の型や、1バイトのビット数、 std::bad_alloc::what のテキストなど。
  • 実装定義動作の一部は ロケール固有動作 であり、実装が提供する locale に依存します。
  • 未規定動作 (unspecified behavior) - プログラムの動作は実装によって異なり、適合する実装は各動作の影響を文書化する必要はありません。
  • 例えば、 評価順序 、同一の string literals が区別されるかどうか、配列割り当てのオーバーヘッド量など。
  • 各々の未規定動作は、有効な結果の集合のうちの1つをもたらす。
  • 誤った動作 (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 - プログラムの動作に制限はありません。
  • 未定義動作の例としては、データ競合、配列境界外へのメモリアクセス、符号付き整数オーバーフロー、nullポインタのデリファレンス、 同一スカラーに対する複数回の 変更 (中間シーケンスポイントなし) (C++11まで) (順序付けされていない) (C++11以降) 異なる型のポインタを通じたオブジェクトへのアクセス などが挙げられる。
  • 実装は未定義動作の診断を要求されず(ただし多くの単純な状況は診断される)、コンパイルされたプログラムは有意義な動作をすることが要求されない。
  • 実行時未定義動作 - 式の評価が core constant expression として行われる場合を除いて未定義となる動作。
(C++11以降)

UBと最適化

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

例えば、

符号付きオーバーフロー

int foo(int x)
{
    return x + 1 > x; // true または符号付きオーバーフローによる未定義動作
}

以下のようにコンパイルされる可能性があります ( demo )

foo(int):
        mov     eax, 1
        ret

範囲外アクセス

int table[4] = {};
bool exists_in_table(int v)
{
    // 最初の4回の反復のいずれかでtrueを返す、さもなければ範囲外アクセスによる未定義動作が発生
    for (int i = 0; i <= 4; i++)
        if (table[i] == v)
            return true;
    return false;
}

以下のようにコンパイルされる可能性があります ( demo )

exists_in_table(int):
        mov     eax, 1
        ret

未初期化スカラー

std::size_t f(int x)
{
    std::size_t a;
    if (x) // xが非ゼロ、または未定義動作
        a = 42;
    return a;
}

以下のようにコンパイルされる可能性があります ( demo )

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

無効なスカラー

int f()
{
    bool b = true;
    unsigned char* p = reinterpret_cast<unsigned char*>(&b);
    *p = 10;
    // bからの読み取りは未定義動作となる
    return b == 0;
}

以下のようにコンパイルされる可能性があります ( demo )

f():
        mov     eax, 11
        ret

ヌルポインタデリファレンス

この例は、nullポインタのデリファレンス結果からの読み取りを示しています。

int foo(int* p)
{
    int x = *p;
    if (!p)
        return x; // 上記のUB、またはこの分岐は決して実行されない
    else
        return 0;
}
int bar()
{
    int* p = nullptr;
    return *p; // 無条件のUB
}

以下のようにコンパイルされる可能性があります ( demo )

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

std::realloc に渡されたポインタへのアクセス 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

副作用のない無限ループ

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!

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

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

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

#include <iostream>
// 例の調整、定数を使用しないこと
double a{1.0};
// C++23標準、§9.4.5 リスト初期化 [dcl.init.list]、例 #6:
struct S
{
    // 初期化子リストコンストラクタなし
    S(int, double, double); // #1
    S();                    // #2
    // ...
};
S s1 = {1, 2, 3.0}; // OK、#1を呼び出し
S s2{a, 2, 3}; // エラー: 縮小変換
S s3{}; // OK、#2を呼び出し
// — 例終わり]
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.

参考文献

拡張コンテンツ
  • 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標準 (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標準 (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標準 (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標準 (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]

関連項目

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

外部リンク

1. LLVMプロジェクトブログ: Cプログラマーが未定義動作について知っておくべきこと #1/3
2. LLVMプロジェクトブログ: Cプログラマーが未定義動作について知っておくべきこと #2/3
3. LLVMプロジェクトブログ: Cプログラマーが未定義動作について知っておくべきこと #3/3
4. 未定義動作はタイムトラベルを引き起こす可能性がある(他にも様々な影響があるが、タイムトラベルが最も奇妙)
5. C/C++における整数オーバーフローの理解
6. NULLポインタで遊ぶ、パート1 (nullポインタ参照による未定義動作が原因のLinux 2.6.30でのローカルエクスプロイト)
7. 未定義動作とフェルマーの最終定理
8. C++プログラマーのための未定義動作ガイド