未定義行為
來自 cppreference.com
C 語言標準精確地指定了 C 語言程式的可觀察行為,除了以下類別:
- 未定義行為 - 對程式的行為沒有限制。未定義行為的例子包括:記憶體訪問超出陣列邊界、有符號整數溢位、空指標解引用、在沒有序列點的情況下,表示式中對同一標量多次修改、透過不同型別的指標訪問物件等。編譯器不要求診斷未定義行為(儘管許多簡單情況會診斷),並且編譯後的程式不要求執行任何有意義的操作。
- 未指定行為 - 允許兩種或多種行為,並且實現不要求記錄每種行為的效果。例如,求值順序,相同的字串字面量是否不同等。每種未指定行為都會產生一組有效結果中的一個,並且在同一程式中重複時可能會產生不同的結果。
- 實現定義行為 - 未指定行為,但每個實現都記錄瞭如何做出選擇。例如,位元組中的位數,或者有符號整數右移是算術移位還是邏輯移位。
(注意:嚴格符合的程式不依賴於任何未指定、未定義或實現定義的行為)
編譯器必須對任何違反 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 行為 (p: 待定)
- 4 一致性 (p: 待定)
- 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++ 文件,關於 未定義行為
|
[編輯] 外部連結
1. | 每個 C 程式設計師都應該知道的關於未定義行為 #1/3 |
2. | 每個 C 程式設計師都應該知道的關於未定義行為 #2/3 |
3. | 每個 C 程式設計師都應該知道的關於未定義行為 #3/3 |
4. | 未定義行為可能導致時間旅行(以及其他事情,但時間旅行是最有趣的) |
5. | 理解 C/C++ 中的整數溢位 |
6. | 未定義行為和費馬大定理 |
7. | 空指標的樂趣,第 1 部分(由於空指標解引用導致的 UB 在 Linux 2.6.30 中引發的本地漏洞) |