Namespaces
Variants

Undefined behavior

From cppreference.net

C言語の標準規格は、以下のカテゴリを除くC言語プログラムの 観測可能な動作 を厳密に規定しています:

  • undefined behavior - プログラムの動作に対する制限は存在しない。未定義動作の例としては、配列境界外へのメモリアクセス、符号付き整数オーバーフロー、ヌルポインタのデリファレンス、シーケンスポイントなしの式内での同一スカラーの 複数回の変更 、異なる型のポインタを通じたオブジェクトへのアクセスなどがある。コンパイラは未定義動作の診断を要求されず(ただし多くの単純な状況は診断される)、コンパイルされたプログラムは有意義な動作をすることが要求されない。
  • 未規定動作 (unspecified behavior) - 2つ以上の動作が許可されており、実装は各動作の効果を文書化する必要はありません。例えば、 評価順序 (order of evaluation) 、同一の 文字列リテラル (string literals) が区別されるかどうかなど。各未規定動作は有効な結果のセットから1つの結果をもたらし、同じプログラム内で繰り返した場合に異なる結果を生成する可能性があります。
  • implementation-defined behavior - 各実装が選択方法を文書化する未規定の動作。例えば、バイト内のビット数、符号付き整数の右シフトが算術シフトか論理シフトかなど。

(注: 厳密に適合した プログラムは、未規定、未定義、または処理系定義の動作に依存しません)

コンパイラは、C++の構文規則や意味制約に違反するプログラムに対して、その動作が未定義または実装定義として指定されている場合や、コンパイラがそのようなプログラムを受け入れる言語拡張を提供している場合でも、診断メッセージ(エラーまたは警告)を発行する必要があります。未定義動作に対する診断は、それ以外では要求されません。

目次

UBと最適化

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

例えば、

符号付きオーバーフロー

int foo(int x)
{
    return x + 1 > x; // trueまたは符号付きオーバーフローによるUBのいずれか
}

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

foo:
        mov     eax, 1
        ret

範囲外アクセス

int table[4] = {0};
int exists_in_table(int v)
{
    // 最初の4回の反復のいずれかで1を返す、または範囲外アクセスによる未定義動作
    for (int i = 0; i <= 4; i++)
        if (table[i] == v)
            return 1;
    return 0;
}

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

exists_in_table:
        mov     eax, 1
        ret

未初期化スカラー

_Bool p; // 初期化されていないローカル変数
if (p) // 未初期化スカラーへのUBアクセス
    puts("p is true");
if (!p) // 未初期化スカラーへのUBアクセス
    puts("p is false");

以下の出力を生成する可能性があります(古いバージョンのgccで観測されたもの):

p は true
p は false
size_t f(int x)
{
    size_t a;
    if (x) // xが非ゼロの場合、または未定義動作
        a = 42;
    return a;
}

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

f:
        mov     eax, 42
        ret

無効なスカラー

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

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

f:
        mov     eax, 11
        ret

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

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

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

foo:
        xor     eax, eax
        ret
bar:
        ret

reallocに渡されたポインタへのアクセス realloc

clangを選択して表示される出力を確認してください

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int *p = (int*)malloc(sizeof(int));
    int *q = (int*)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
        printf("%d%d\n", *p, *q);
}

出力例:

12

副作用のない無限ループ

clangを選択して表示される出力を確認してください

#include <stdio.h>
int fermat()
{
    const int MAX = 1000;
    // Endless loop with no side effects is UB
    for (int a = 1, b = 1, c = 1; 1;)
    {
        if (((a * a * a) == ((b * b * b) + (c * c * c))))
            return 1;
        ++a;
        if (a > MAX)
        {
            a = 1;
            ++b;
        }
        if (b > MAX)
        {
            b = 1;
            ++c;
        }
        if (c > MAX)
            c = 1;
    }
    return 0;
}
int main(void)
{
    if (fermat())
        puts("Fermat's Last Theorem has been disproved.");
    else
        puts("Fermat's Last Theorem has not been disproved.");
}

出力例:

Fermat's Last Theorem has been disproved.

参考文献

  • C23規格 (ISO/IEC 9899:2024):
  • 3.4 動作 (p: TBD)
  • 4 適合性 (p: TBD)
  • C17規格 (ISO/IEC 9899:2018):
  • 3.4 動作 (p: 3-4)
  • 4 適合性 (p: 8)
  • C11規格 (ISO/IEC 9899:2011):
  • 3.4 動作 (p: 3-4)
  • 4/2 未定義動作 (p: 8)
  • C99規格 (ISO/IEC 9899:1999):
  • 3.4 動作 (p: 3-4)
  • 4/2 未定義動作 (p: 7)
  • C89/C90標準 (ISO/IEC 9899:1990):
  • 1.6 用語の定義

関連項目

C++ documentation for Undefined behavior
**日本語訳:**
C++ documentation for Undefined behavior
**翻訳結果:** - "C++ documentation" はC++専門用語のため翻訳せず - "Undefined behavior" はC++専門用語のため翻訳せず - HTMLタグ、属性、構造は完全に保持 - テキスト部分のみを翻訳対象とし、指示に従ってC++関連用語は原文のまま維持

外部リンク

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