除錯與工具

未定義行為消毒器(UndefinedBehaviorSanitizer, UBSan)

/ UBSan -> YOO-bee-san /

C 有一長串你根本不被允許做的事——讓有號整數溢位、位移太多個位元、解參考一個未對齊的指標、除以零、索引超出邊界。令人不安的是標準並不要求編譯器警告或當掉;做這些是未定義行為,而程式可能看起來能動,直到有一天最佳化器假設你從未做過、然後悄悄壞掉。UBSan 是一個把這許多無聲地雷變成大聲、即時抱怨的工具,讓你在它們發生的那一刻就看見。

具體來說,UBSan 是 gcc 與 clang 裡的一種消毒器,用 -fsanitize=undefined 啟用(或像 -fsanitize=signed-integer-overflow 這種更細的旗標)。它在編譯期於有風險的操作之前插入小小的執行期檢查:在一次有號加法之前檢查溢位、在一次位移之前檢查位移量在範圍內、在一次解參考之前檢查對齊與非空、在一次陣列索引之前可以檢查邊界。當某個檢查失敗,UBSan 印出一條清楚的訊息,指名那個確切的未定義行為與原始碼行——例如「runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type int」。它預設是回報後繼續執行;搭配 -fno-sanitize-recover 或 -fsanitize-trap 則在第一次違規就中止。速度損失很小,且它可與位址消毒器併用。

為何重要:未定義行為是 C 裡「除錯版能動、發行版壞掉」與「用了好幾年、然後莫名其妙不行」這類臭蟲最深層的來源,正因為沒有任何東西逼它顯眼地失敗。UBSan 把看不見的變成看得見:它在有號溢位、壞位移、空指標解參考發生的瞬間就逮到、附上行號,而不是讓最佳化器日後去利用它們。誠實的提醒:它涵蓋許多但非全部形式的未定義行為(它無法逮到每一種別名或釋放後使用的情況——那些屬於 ASan 與嚴格別名規則),而且和所有消毒器一樣,它只看得到你執行走到的程式路徑。在測試時把它跟 -Wall 與 ASan 一起開著。

$ gcc -g -fsanitize=undefined ovf.c -o a 對一段做 int x = INT_MAX; x = x + 1; 的程式碼,在執行期印出:「ovf.c:5:9: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type int」——指名那個未定義行為與確切的行。

UBSan 在有號整數溢位發生的那一刻就標出它,附上檔名與行號。

記得有號溢位是未定義行為,即使硬體會環繞——UBSan 之所以回報它,正因為標準禁止它,而無號運算依定義就會環繞、不會被標出。UBSan 涵蓋許多未定義行為類別、但非全部,且只涵蓋你測試實際執行到的路徑。

又称
UBSan-fsanitize=undefinedUB sanitizer