Undefined behavior
C言語の標準規格は、以下のカテゴリを除くC言語プログラムの 観測可能な動作 を厳密に規定しています:
- undefined behavior - プログラムの動作に対する制限は存在しない。未定義動作の例としては、配列境界外へのメモリアクセス、符号付き整数オーバーフロー、ヌルポインタのデリファレンス、シーケンスポイントなしの式内での同一スカラーの 複数回の変更 、異なる型のポインタを通じたオブジェクトへのアクセスなどがある。コンパイラは未定義動作の診断を要求されず(ただし多くの単純な状況は診断される)、コンパイルされたプログラムは有意義な動作をすることが要求されない。
- 未規定動作 (unspecified behavior) - 2つ以上の動作が許可されており、実装は各動作の効果を文書化する必要はありません。例えば、 評価順序 (order of evaluation) 、同一の 文字列リテラル (string literals) が区別されるかどうかなど。各未規定動作は有効な結果のセットから1つの結果をもたらし、同じプログラム内で繰り返した場合に異なる結果を生成する可能性があります。
- implementation-defined behavior - 各実装が選択方法を文書化する未規定の動作。例えば、バイト内のビット数、符号付き整数の右シフトが算術シフトか論理シフトかなど。
- ロケール固有の動作 - 実装定義の動作で、 現在選択されているロケール に依存します。例えば、 islower が26の小文字ラテン文字以外の文字に対してtrueを返すかどうか。
(注: 厳密に適合した プログラムは、未規定、未定義、または処理系定義の動作に依存しません)
コンパイラは、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
未初期化スカラー
以下の出力を生成する可能性があります(古いバージョンのgccで観測されたもの):
p は true p は false
以下のようにコンパイルされる可能性があります ( 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を選択して表示される出力を確認してください
出力例:
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
|
外部リンク
| 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ローカルエクスプロイト) |