整數溢出與格式化輸出是 C 語言最基礎也最易踩坑的知識點。本文結合《C Primer Plus》第 2 章內容,通過實測代碼深度解析 有符號/無符號溢出的環模型本質、printf 格式符與實際類型不匹配時的位模式解讀、八/十六進制輸出細節,建立正確的數據存儲與輸出心智模型。
int i = 2147483647; // INT_MAX (2^31-1)
unsigned int j = 4294967295; // UINT_MAX (2^32-1)
printf("%d %d %d\n", i, i+1, i+2); // 2147483647 -2147483648 -2147483647
printf("%u %u %u\n", i, i+1, i+2); // 2147483647 2147483648 2147483649
printf("%d %d %d\n", j, j+1, j+2); // -1 0 1
printf("%u %u %u\n", j, j+1, j+2); // 4294967295 0 1
計算機中整數以 固定寬度二進制補碼 存儲,本質是 模 2N 的循環群:
[0, 2^N-1],溢出等價於 mod 2^N,行為**確定**。[-2^(N-1), 2^(N-1)-1],同樣按補碼環運算,但 C 標準規定**有符號溢出 = 未定義行為 (UB)**,編譯器可假設不發生並優化。實際運行中多數編譯器仍按補碼環執行。| 表達式 | 補碼位模式 (32位) | %d 解讀 (有符號) |
%u 解讀 (無符號) |
|---|---|---|---|
i (INT_MAX) |
0x7FFFFFFF (0111...111) |
2147483647 | 2147483647 |
i+1 |
0x80000000 (1000...000) |
-2147483648 (INT_MIN) | 2147483648 |
i+2 |
0x80000001 (1000...001) |
-2147483647 | 2147483649 |
j (UINT_MAX) |
0xFFFFFFFF (1111...111) |
-1 | 4294967295 |
j+1 |
0x00000000 (溢出歸零) |
0 | 0 |
j+2 |
0x00000001 |
1 | 1 |
核心結論:**內存中的位模式不變,只是解讀方式 (格式符) 不同**。%d 將最高位視為符號位,%u 視為數值位。這就是「同一份二進制,既可以是有符號數,也可以是無符號數」的真諦。
unsigned int un = 3000000000; // 0xB2D05E00
short end = 200; // 0x00C8
long big = 65537; // 0x00010001
long long verybig = 12345678908642; // 0x0000B3E6 5D5E5E2 (64位)
printf("%u, %d\n", un, un); // 3000000000, -1294967296
printf("%hd, %d\n", end, end); // 200, 200
printf("%ld, %hd\n", big, big); // 65537, 1
printf("%lld, %ld\n", verybig, verybig); // 12345678908642, 1942899938
printf 是可變參函數,**默認參數提升**:float→double,char/short→int,其餘保持。調用方不傳類型信息。printf 根據格式符從棧/寄存器取指定字節數,按格式符語義解讀位模式。%hd 取低 16 位 (short);%d 取 32 位 (int);%ld 取 32/64 位 (long);%lld 取 64 位。un = 3000000000 (0xB2D05E00):%u 正確顯示;%d 視為有符號,最高位 1 → 負數,補碼值 -1294967296。end = 200 (0x00C8):提升為 int 0x000000C8,%hd 取低 16 位 = 200,%d 取 32 位 = 200。big = 65537 (0x00010001):%ld 正確顯示 65537;%hd 只取低 16 位 0x0001 = 1。verybig:64 位值。%lld 正確顯示;%ld (32 位 long) 只取低 32 位 0x5D5E5E2 = 1942899938。# 標誌的作用
int x = 100; // 0x64, 0144
printf("%d %o %x\n", x, x, x); // 100 144 64
printf("%d %#o %#x\n", x, x, x); // 100 0144 0x64
%o 八進制,%x 十六進制小寫,%X 大寫。# 標誌:八進制加前綴 0,十六進制加 0x/0X。%d 看是有符號,用 %u 看是無符號,用 %x 看是十六進制。**printf 不知類型,只信格式符**。if (a > INT_MAX - b) overflow。long long 用 %lld,size_t 用 %zu (C99),intptr_t 用 %" PRIdPTR 。%p (配合 void*),不可用 %x/%lu。-Wformat -Wformat-security -Wconversion,捕獲 90% 不匹配 Bug。