名稱空間
變體
操作

未定義行為

來自 cppreference.com
< c‎ | language

C 語言標準精確地指定了 C 語言程式的可觀察行為,除了以下類別:

  • 未定義行為 - 對程式的行為沒有限制。未定義行為的例子包括:記憶體訪問超出陣列邊界、有符號整數溢位、空指標解引用、在沒有序列點的情況下,表示式中對同一標量多次修改、透過不同型別的指標訪問物件等。編譯器不要求診斷未定義行為(儘管許多簡單情況會診斷),並且編譯後的程式不要求執行任何有意義的操作。
  • 未指定行為 - 允許兩種或多種行為,並且實現不要求記錄每種行為的效果。例如,求值順序,相同的字串字面量是否不同等。每種未指定行為都會產生一組有效結果中的一個,並且在同一程式中重複時可能會產生不同的結果。
  • 實現定義行為 - 未指定行為,但每個實現都記錄瞭如何做出選擇。例如,位元組中的位數,或者有符號整數右移是算術移位還是邏輯移位。
  • 區域設定特定行為 - 依賴於當前選定的區域設定的實現定義行為。例如,對於除 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 行為 (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 中引發的本地漏洞)