名前空間
変種
操作

未定義な振る舞い

From cppreference.com
< c‎ | language

C言語標準は、以下のカテゴリに属するプログラムを除き、C言語プログラムの観測可能な動作を正確に規定しています。

  • 未定義の動作 - プログラムの動作に制限はありません。未定義の動作の例としては、配列境界外のメモリアクセス、符号付き整数オーバーフロー、ヌルポインタの逆参照、シーケンスポイントなしの式で同じスカラー値を複数回変更すること、異なる型のポインタを介したオブジェクトへのアクセスなどが挙げられます。コンパイラは未定義の動作を診断する義務はありません(ただし、多くの単純な状況は診断されます)、またコンパイルされたプログラムが意味のあることを行う義務もありません。
  • 未規定の動作 - 2つ以上の動作が許可されており、実装はその各動作の効果を文書化する義務はありません。例としては、評価順序、同一の文字列リテラルが別個のものかどうかが挙げられます。未規定の動作は、許可された結果のセットのいずれか1つになり、同じプログラムで繰り返し実行された場合に異なる結果を生成する可能性があります。
  • 処理系定義の動作 - 各処理系が選択方法を文書化する未規定の動作です。例としては、バイトあたりのビット数、または符号付き整数の右シフトが算術シフトか論理シフトかなどが挙げられます。
  • ロケール依存の動作 - 現在選択されているロケールに依存する処理系定義の動作です。例としては、26個の小文字のラテン文字以外の文字に対してislowerがtrueを返すかどうかなどが挙げられます。

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

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

目次

[編集] UBと最適化

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

例えば、

[編集] 符号付きオーバーフロー

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

デモ)のようにコンパイルされることがあります。

foo:
        mov     eax, 1
        ret

[編集] 境界外アクセス

int table[4] = {0};
int exists_in_table(int v)
{
    // return 1 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 1;
    return 0;
}

デモ)のようにコンパイルされることがあります。

exists_in_table:
        mov     eax, 1
        ret

[編集] 未初期化スカラー

_Bool p; // uninitialized local variable
if (p) // UB access to uninitialized scalar
    puts("p is true");
if (!p) // UB access to uninitialized scalar
    puts("p is false");

(古いバージョンのgccで観測された)以下の出力が生成されることがあります。

p is true
p is false
size_t f(int x)
{
    size_t a;
    if (x) // either x nonzero or UB
        a = 42;
    return a;
}

デモ)のようにコンパイルされることがあります。

f:
        mov     eax, 42
        ret

[編集] 無効なスカラー

int f(void)
{
    _Bool b = 0;
    unsigned char* p = (unsigned char*)&b;
    *p = 10;
    // reading from b is now UB
    return b == 0;
}

デモ)のようにコンパイルされることがあります。

f:
        mov     eax, 11
        ret

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

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 = NULL;
    return *p;    // Unconditional UB
}

デモ)のようにコンパイルされることがあります。

foo:
        xor     eax, eax
        ret
bar:
        ret

[編集] 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 Behavior (p: TBD)
  • 4 Conformance (p: TBD)
  • C17標準 (ISO/IEC 9899:2018)
  • 3.4 Behavior (p: 3-4)
  • 4 Conformance (p: 8)
  • C11標準 (ISO/IEC 9899:2011)
  • 3.4 Behavior (p: 3-4)
  • 4/2 Undefined behavior (p: 8)
  • C99標準 (ISO/IEC 9899:1999)
  • 3.4 Behavior (p: 3-4)
  • 4/2 Undefined behavior (p: 7)
  • C89/C90標準 (ISO/IEC 9899:1990)
  • 1.6 DEFINITIONS OF TERMS

[編集] 関連項目

C++ のドキュメント (未定義の動作)

[編集] 外部リンク

1.  What Every C Programmer Should Know About Undefined Behavior #1/3
2.  What Every C Programmer Should Know About Undefined Behavior #2/3
3.  What Every C Programmer Should Know About Undefined Behavior #3/3
4.  未定義の動作は、タイムトラベル(その他もろもろ、しかしタイムトラベルが最も奇妙なもの)につながる可能性があります。
5.  Understanding Integer Overflow in C/C++
6.  Undefined Behavior and Fermat’s Last Theorem
7.  Fun with NULL pointers, part 1 (Linux 2.6.30におけるヌルポインタの逆参照によるUBが原因のローカルエクスプロイト)
English 日本語 中文(简体) 中文(繁體)