什麼是未定義行為

C或C++的標準對結果沒有任何要求的行為。例如有號整數溢位、陣列範圍外的讀寫、參考 NULL 指標、使用已釋放的區域、負值的左移等。編譯器可以假設正確的程式不會發生未定義行為來進行最佳化,因此程式可能碰巧照預期運作,也可能因最佳化設定而使判斷消失等,出現意料之外的結果。它與由處理系統決定並寫在文件中的「實作定義行為」有所區別。

日文名稱: 未定義動作

「未定義行為」出現在2領域的10道練習題中(C語言基礎, 編譯器等)。

這個名詞所屬的領域: C語言基礎

代表性的例子

有號整數運算的溢位(如 INT_MAX + 1)、陣列範圍外的讀寫、使用未初始化自動變數的值、透過 NULL 指標或懸置指標讀寫、同一區域的重複釋放、以0做整數除法、移位量達型別寬度以上或負值的左移等。無號整數的溢位不是未定義行為,規定會回到除以2的冪次的餘數。

與編譯器最佳化的關係

編譯器可以假設「不會發生未定義行為」,因此可能把 x + 1 > x 視為恆真,或刪除讀取 *p 之後的 p == NULL 判斷。所以在 -O0 下照預期運作的程式,可能只因改成 -O2 或升級編譯器就無法運作。這不是編譯器的錯誤,原因在於程式違反了標準的前提。

發現與預防的方法

gcc 或 clang 加上 -fsanitize=undefined(UBSan)可在執行時偵測有號整數溢位等,加上 -fsanitize=address(ASan)可偵測範圍外的讀寫與釋放後使用。-Wall 或 -Wextra 的警告、靜態分析工具,以及遵循 MISRA C 等規範的寫法,也有助於減少未定義行為。

練習未定義行為的題目

關於C語言標準所說的未定義行為,下列說明何者正確?

  1. A標準沒有作任何規定的行為,編譯器可以假設它不會發生來進行最佳化,也可能出現意料之外的結果
  2. B由各處理系統決定結果,並且有義務把結果寫在文件中的行為
  3. C在執行時一定會被偵測,顯示錯誤訊息並停止程式的行為
查看答案與解析

正確答案:A. 標準沒有作任何規定的行為,編譯器可以假設它不會發生來進行最佳化,也可能出現意料之外的結果

未定義行為是標準不作任何要求的行為,例如有號整數溢位、陣列範圍外的參考、參考 NULL 指標、釋放後使用等。程式可能碰巧照預期運作,但編譯器可以假設「正確的程式中不會發生」來進行最佳化,因此只要改變最佳化設定或編譯器版本,結果就可能改變。相對地,像 int 的大小、char 是否有號這類由處理系統決定且必須寫在文件中的事項,稱為實作定義行為。gcc 或 clang 加上 -fsanitize=undefined,可以在執行時偵測部分未定義行為。

int 與 unsigned int 為32位元。下列何者依C語言的標準屬於未定義行為?

  1. Aunsigned int 變數的值為最大值4294967295時,再加1
  2. B以 >> 把 int 的負值右移1位元
  3. Cint 變數的值為最大值2147483647時,再加1
查看答案與解析

正確答案:C. int 變數的值為最大值2147483647時,再加1

像 int 的 2147483647 + 1 這樣,有號整數運算的結果超出可表示的範圍,屬於未定義行為。無號整數的運算規定會回到除以 2³² 的餘數,因此 4294967295 + 1 為0,不是未定義行為。負值的右移是由處理系統決定為算術移位或邏輯移位的實作定義行為,gcc 採用算術移位。另一方面,負值的左移是未定義行為。

常見問題

未定義行為與實作定義行為有什麼不同?

實作定義行為是像 int 的大小、char 是否有號、負值右移的結果這類,由處理系統選定一種行為並寫在文件中的事項。未定義行為是標準不作任何要求的行為,即使是同一個處理系統,結果也可以因情況而異。具可攜性的程式會明示依賴實作定義行為的部分,並避免發生未定義行為。

有未定義行為但能運作,就沒有問題嗎?

有問題。即使在目前的環境中碰巧照預期運作,只要最佳化設定、編譯器版本或周圍的程式碼改變,結果就可能不同。從資安的角度來看,範圍外寫入這類未定義行為也可能被攻擊者利用。

解析中的「未定義行為」

出現「未定義行為」的題目(10題)

  1. 在C語言中,有號整數的溢位是未定義行為。在此前提下,對上方函式進行最佳化的編譯器可能產生的程式碼,下列何者正確?
  2. 關於C語言標準所說的未定義行為,下列說明何者正確?
  3. int 與 unsigned int 為32位元。下列何者依C語言的標準屬於未定義行為?
  4. uint8_t 是8位元的無號整數型別。執行上方讓 x 遞增10次的程式碼時的輸出,下列何者正確?
  5. int 為32位元(最大值 2147483647)。上方的函式 fact(n) 能不發生溢位、正確回傳 n 的階乘的最大 n 為下列何者?
  6. 關於上方函式 make 所回傳的指標,下列何者正確?
  7. C語言中,建議在 free(p); 之後立刻寫 p = NULL; 的理由,下列何者最適當?
  8. C語言中,參考 NULL 指標(p 為 NULL 時讀取 *p)時的說明,下列何者正確?
  9. 執行上方使用以 #define 定義之巨集 SQUARE 的程式碼時的輸出,下列何者正確?
  10. 以 gcc 啟用最佳化來編譯上方的函式時,if (p == NULL) 的判斷可能被刪除。其理由為下列何者?

相關名詞

指標(pointer) 變數 陣列 遞迴 堆疊 懸置指標 回傳值 動態記憶體配置 頁面錯誤 微控制器(MCU)

前往名詞解釋列表