未定義な振る舞い
From cppreference.com
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
[編集] 未初期化スカラー
(古いバージョンのgccで観測された)以下の出力が生成されることがあります。
p is true p is false
(デモ)のようにコンパイルされることがあります。
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を選択してください。
このコードを実行
実行結果の例
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++ のドキュメント (未定義の動作)
|