未定義的行為
什麼是未定義行為(UB)?
未定義的行為是 C 標準中使用的術語。C11 標準(ISO / IEC 9899:2011)將術語未定義行為定義為
行為,在使用不可移植或錯誤的程式結構或錯誤資料時,本國際標準不對此要求
如果我的程式碼中有 UB 會怎麼樣?
根據標準,這些結果可能由於未定義的行為而發生:
注意可能的未定義行為包括完全忽略具有不可預測結果的情況,在轉換或程式執行期間以環境特徵(有或沒有發出診斷訊息)的文件方式執行,終止轉換或執行(使用發出診斷資訊)。
以下引用通常用於描述(不太正式)從未定義的行為發生的結果:
“當編譯器遇到[給定的未定義構造]時,讓惡魔飛出你的鼻子是合法的”(這意味著編譯器可以選擇任何奇怪的方式來解釋程式碼而不違反 ANSI C 標準)
為什麼 UB 存在?
如果它是如此糟糕,為什麼他們不只是定義它或使其實現定義?
未定義的行為允許更多優化機會; 編譯器可以合理地假設任何程式碼都不包含未定義的行為,這可以允許它避免執行時檢查並執行其有效性成本高昂或無法證明的優化。
為什麼 UB 難以追蹤?
至少有兩個原因導致未定義的行為導致難以檢測的錯誤:
- 編譯器不需要 - 通常不能可靠 - 警告你未定義的行為。實際上要求它這樣做會直接反對存在未定義行為的原因。
- 不可預測的結果可能無法在操作的確切位置開始展開,其中行為未定義的構造發生; 未定義的行為會影響整個執行,其影響可能隨時發生:在未定義的構造期間,之後或甚至之前。
考慮空指標解除引用:編譯器不需要診斷空指標解除引用,甚至不能,因為在執行時傳遞給函式或全域性變數的任何指標都可能為 null。*當空指標解引用發生時,標準不要求程式需要崩潰。*相反,程式可能會更早,更晚或者根本不崩潰; 它甚至可以表現為空指標指向有效物件,並且表現完全正常,僅在其他情況下崩潰。
在空指標解除引用的情況下,C 語言與 Java 或 C#等託管語言不同,其中定義了空指標解除引用的行為 :在確切的時間丟擲異常(Java 中的 NullPointerException
,C#中的 NullReferenceException
)因此來自 Java 或 C#的那些人可能錯誤地認為在這種情況下,無論是否發出診斷訊息,C 程式都必須崩潰。
附加資訊
有幾種這樣的情況應該明確區分:
- 顯式未定義的行為,即 C 標準明確告訴你不受限制的行為。
- 隱式未定義的行為,標準中根本沒有文字預見到你帶來程式的情況的行為。
還要記住,在許多地方,C 標準故意未定義某些構造的行為,以便為編譯器和庫實現者提供自己定義的空間。一個很好的例子是訊號和訊號處理程式,其中 C 的擴充套件(例如 POSIX 作業系統標準)定義了更詳細的規則。在這種情況下,你只需檢查平臺的文件; C 標準無法告訴你任何事情。
還要注意,如果在程式中發生未定義的行為,並不意味著僅發生未定義行為的點是有問題的,而是整個程式變得毫無意義。
由於存在這些問題,因此在 C 語言中程式設計人員至少熟悉觸發未定義行為的事物時,這一點很重要(特別是因為編譯器並不總是警告我們關於 UB)。
應該注意的是,有一些工具(例如靜態分析工具,如 PC-Lint)有助於檢測未定義的行為,但同樣,它們無法檢測到所有出現的未定義行為。