未定義行為與安全

記憶體安全與型別安全

一個語言能對正確性做出的兩種不同承諾常被混為一談,但它們防的是不同的錯誤,把它們分清楚能磨利你對 C、以及對更安全語言的思考。一個承諾是:你的程式只會碰它被允許碰的記憶體。另一個是:它只會把一段資料當成它「實際所是」的那種東西。一個語言可以只有其中之一。

記憶體安全意謂每次存取都停留在一個有效、存活的物件之內:不讀寫陣列尾端之外、不在所指之物消失後還使用指標、不解參考空指標或野指標。當記憶體安全被違反時,你得到的正是本頁的這些臭蟲——越界存取、釋放後使用、緩衝區溢位。型別安全意謂值只能照其宣告型別允許的方式使用:你不能悄悄把一個整數的位元重新詮釋成指標、或透過簽章錯誤的指標去呼叫函式,除非經過明確、有檢查的轉換。C 在這兩方面都是出了名地「弱」。它的轉型讓你跨型別重新詮釋位元組(違反型別安全、常常也違反嚴格別名),而它完全沒有邊界檢查與生命期追蹤,讓你用一個跑掉的索引或一個懸置指標就違反記憶體安全。那份自由,等量地是 C 的威力與危險的來源。

為什麼重要:C 裡大多數災難性、可被利用的臭蟲都是記憶體安全違反,這就是業界為何大力推向記憶體安全語言——Rust 透過所有權與借用檢查在編譯期強制記憶體安全,而像 Java、Go 這類語言則靠執行期的邊界檢查與垃圾回收達成。值得記住的誠實細節:記憶體安全與型別安全減少整「類」臭蟲,但既不消除邏輯錯誤、也不消除每一個瑕疵——一支記憶體安全的程式仍可能算出錯誤答案、發生死結、或洩漏資源。安全是移除一整類災難的「地板」,不是保證正確的「天花板」。

/* 記憶體安全違反:索引超過尾端 */ int a[3]; a[5] = 1; /* 型別安全違反:把一個 int 的位元重新詮釋為指標 */ int n = 0x1234; int *p = (int *)(intptr_t)n; /* 現在 *p 會碰到位址 0x1234 */

第一行破壞記憶體安全(越界);第二行破壞型別安全(把整數當成位址)。C 兩者都允許。

記憶體安全與型別安全減少整類臭蟲,但不保證正確——一支完全記憶體安全、型別安全的程式仍可能有邏輯臭蟲、死結與洩漏。別把任一者吹成「再也沒有臭蟲」。

又称
memory safetytype safety記憶體安全型別安全