這是一道經典面試題,考察計算機組成原理中定點數表示、補碼運算、溢位標誌的核心理解。本文從硬體邏輯、Java 語言行為、C/C++ 未定義行為、防禦性編程四個維度徹底講透。
CPU 通用寄存器位寬固定 (8/16/32/64 位)。以 8 位為例:
正數:符號位 0,數值位直接表示 0 = 0000 0000 1 = 0000 0001 127 = 0111 1111 (最大正數) 負數:符號位 1,數值位 = 絕對值取反 + 1 -1 = 1111 1111 (0xFF) -2 = 1111 1110 -128 = 1000 0000 (最小負數,無對應正數)
ALU 加法器不區分有符號/無符號,僅做二進制加法並產生標誌位:
0111 1111 (127) + 0000 0001 (1) ----------- 1000 0000 (-128) ← 結果位模式 CF = 0 (無進位到第 9 位) OF = 1 (符號位從 0 變 1,發生有符號溢位) 有符號解讀:127 + 1 = -128 → n+1 < n 成立! 無符號解讀:127 + 1 = 128 → 正常
public class OverflowDemo {
public static void main(String[] args) {
// int (32 位)
int maxInt = Integer.MAX_VALUE; // 2,147,483,647
System.out.println("maxInt + 1 = " + (maxInt + 1)); // -2,147,483,648 (MIN_VALUE)
System.out.println("maxInt + 1 < maxInt ? " + ((maxInt + 1) < maxInt)); // true
// byte (8 位) - 自動提升為 int 運算,需強轉
byte maxByte = Byte.MAX_VALUE; // 127
byte result = (byte) (maxByte + 1); // -128
System.out.println("(byte)(127+1) = " + result); // -128
// 無符號視圖 (Java 8+)
System.out.println("Unsigned: " + Integer.toUnsignedString(maxInt + 1)); // 2147483648
}
}
final int a = 2147483647; int b = a + 1; // 編譯錯誤!常量表達式溢位編譯期報錯 // 需用變量或 Math.addExact int c = Math.addExact(a, 1); // 運行期拋出 ArithmeticException
| 語言 | 有符號溢位 | 無符號溢位 | 編譯器優化風險 |
|---|---|---|---|
| Java | 定義行為:環形補碼 | 同有符號 (無無符號類型) | 無 (語義固定) |
| C/C++ | 未定義行為 (UB) | 定義行為:模 2^N 環形 | 極高:編譯器假設不溢位,可能刪除檢查代碼 |
// C 代碼:危險!
int foo(int x) {
if (x + 1 > x) { // 編譯器假設有符號不溢位,優化為 if (true)
return 1;
}
return 0;
}
// 當 x == INT_MAX 時,行為不可預測 (可能返回 0、1、崩潰...)
// 安全寫法
if (x > INT_MAX - 1) { ... } // 避免溢位運算
// 或使用內建函數
if (__builtin_add_overflow(x, 1, &result)) { ... }
// 拋出 ArithmeticException 而非靜默錯誤 int safeAdd = Math.addExact(a, b); // 加法 int safeSub = Math.subtractExact(a, b); // 減法 int safeMul = Math.multiplyExact(a, b); // 乘法 int safeInc = Math.incrementExact(a); // ++ int safeNeg = Math.negateExact(a); // -a // 轉型檢查 int safeToInt = Math.toIntExact(longValue); // long → int 檢查溢位
// 加法溢位檢查
public static boolean willOverflowAdd(int a, int b) {
if (b > 0) return a > Integer.MAX_VALUE - b;
else return a < Integer.MIN_VALUE - b;
}
// 乘法溢位檢查
public static boolean willOverflowMul(int a, int b) {
if (a == 0 || b == 0) return false;
if (a == Integer.MIN_VALUE || b == Integer.MIN_VALUE) return true; // 絕對值溢位
return Math.abs(a) > Integer.MAX_VALUE / Math.abs(b);
}
// 安全加法
public static int safeAdd(int a, int b) {
if (willOverflowAdd(a, b)) throw new ArithmeticException("Integer overflow");
return a + b;
}
BigDecimal 或長整型分單位 (元→分)(index + 1) % capacity 利用溢位特性31 * hash + val 依賴溢位混淆 (Java HashMap)n+1 < n 成立的本質:固定位寬寄存器 + 補碼表示 + 有符號解讀 導致的環形溢位。Java 將此定義為確定性語義 (靜默截斷),C/C++ 則為未定義行為 (編譯器優化陷阱)。工程上應:優先用長整型/BigDecimal、使用 Math.addExact 等安全 API、關鍵路徑加邊界檢查、單元測試覆蓋邊界值 (MAX_VALUE, MIN_VALUE)。理解溢位機制,是寫出健壯、安全、高效代碼的底層功力。