資料表示法:位元、位元組與數值

整數溢位與環繞

定寬整數就像一個輪數固定的汽車里程表:它只能顯示這麼多位數,而且有個它能顯示的最大數字。整數溢位就是當一個計算試圖產生大到(或小到)裝不進那個寬度的值時所發生的事。就像里程表從 999999 翻回 000000,結果通常會環繞——但我們將看到,在 C 裡那個環繞是否安全,關鍵取決於是否有號。

具體來說,在一個 32 位元整數上,最大值約是 42.9 億(無號)或約 +21.5 億(有號)。對最大值加 1,那個真正的數學答案在這 32 位元裡根本無處可去。對無號型別來說,行為是有定義的:它對 2^n 取模環繞,所以最大值加一變成 0,乾淨且可預測。對「有號」型別,故事就比較黑暗,如附註所述。經典的真實畫面是一個計數器、一個緩衝區大小計算,或一個時間戳,悄悄翻轉並產生一個遠比任何人預期都小的值。

溢位是數量驚人的真實錯誤和安全漏洞的來源:一個算成「a + b」而溢位的長度可能產生一個極小的數字,誘騙程式配置一個小緩衝區,然後往裡面寫入遠多於此的資料。解方不是寄望它永不發生,而是為它而設計——在相加前檢查(a 是否大於 MAX 減 b?)、為中間結果用較寬的型別,或使用內建的檢查算術函式。永遠別假設某個值「不可能變那麼大」;攻擊者和邊界情況會找到它真的變那麼大的那一刻。

在 32 位元無號上,UINT_MAX 是 4294967295;加 1 環繞成 0。在 16 位元有號上,32767 + 1 溢出 INT16_MAX 的範圍,並(在實務上)環繞成 -32768——正是計數器轉成負數的寫照。

超過頂端,值就像里程表一樣翻轉歸位。

關鍵在於:只有「無號」溢位被定義為環繞;「有號」溢位在 C 裡是未定義行為——編譯器可以假設它從不發生,並把你的「if (x + 1 < x)」溢位檢查整個刪掉。要在溢位發生「前」偵測,而非之後。

又稱
arithmetic overflow整數溢位wraparound