經典面試題深度解析:無符號格式符輸出有符號負數
題目回顧
printf("%d,%d,%u\n", 016, 16, -16);
// 前兩個輸出:14, 16
// 第三個 %u 輸出 -16 會是什麼?
核心知識點
| 常量/表達式 | 類型/值 | 說明 |
| 016 | 八進制 int = 14 | 前導 0 表示八進制:1×8 + 6 = 14 |
| 16 | 十進制 int = 16 | 默認十進制 |
| -16 | 有符號 int = -16 | 二進制補碼表示 |
| %u | 無符號十進制格式 | 將對應內存位模式按無符號數解讀 |
內存位模式分析(32 位系統)
-16 的二進制補碼表示:
原碼: 00000000 00000000 00000000 00010000 (16)
反碼: 11111111 11111111 11111111 11101111
補碼: 11111111 11111111 11111111 11110000 ← 內存中實際存儲的位模式
%u 將這 32 位按無符號數解讀:
2³² - 16 = 4,294,967,296 - 16 = 4,294,967,280
十六進制: 0xFFFFFFF0
完整驗證程序
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
int main() {
int a = 016; // 八進制 14
int b = 16; // 十進制 16
int c = -16; // 有符號 -16
unsigned int u = -16; // 隱式轉換
printf("原始題目輸出:\n");
printf("%%d,%%d,%%u: %d,%d,%u\n\n", a, b, c);
printf("詳細分析:\n");
printf("a = 016 (八進制) = %d (十進制)\n", a);
printf("b = 16 (十進制) = %d\n", b);
printf("c = -16 (有符號) = %d\n", c);
printf("c 的十六進制: 0x%X\n", c);
printf("c 作為無符號: %u\n", (unsigned int)c);
printf("UINT_MAX = %u\n", UINT_MAX);
printf("UINT_MAX + 1 - 16 = %u\n\n", (UINT_MAX + 1u) - 16);
printf("相關現象:\n");
printf("-1 作為 %%u: %u (即 UINT_MAX)\n", -1);
printf("-1 作為 %%x: 0x%X\n", -1);
printf("關係: %u = %u + 1 + (-16) = %u\n", (unsigned int)(-16), UINT_MAX, (unsigned int)(-16));
return 0;
}
執行結果
原始題目輸出:
%d,%d,%u: 14,16,4294967280
詳細分析:
a = 016 (八進制) = 14 (十進制)
b = 16 (十進制) = 16
c = -16 (有符號) = -16
c 的十六進制: 0xFFFFFFF0
c 作為無符號: 4294967280
UINT_MAX = 4294967295
UINT_MAX + 1 - 16 = 4294967280
相關現象:
-1 作為 %u: 4294967295 (即 UINT_MAX)
-1 作為 %x: 0xFFFFFFFF
關係: 4294967280 = 4294967295 + 1 + (-16) = 4294967280
核心結論
- %u 不做數值轉換:只是將內存中的位模式按無符號整數規則解讀並輸出
- 有符號轉無符號規則:C 標準規定,負數轉無符號時加 2ⁿ(n 為位寬),即模 2ⁿ 運算
- 實際輸出:
4294967280(32 位系統),而非 16
工程避坑指南
| 危險場景 | 後果 | 修正方案 |
| printf("%u", -1) | 輸出巨大正數,邏輯判斷錯誤 | 用 %d 輸出有符號數,或顯式轉換 (unsigned) |
| 比較 signed/unsigned | -1 > 0U 為真!隱式轉換陷阱 | 編譯器開啟 -Wsign-compare,統一類型 |
| scanf("%u", &signed_var) | 輸入大數導致溢出/負數 | 匹配格式符與變量類型,用 strtoul 解析 |
記憶口訣
「格式符只管解讀位模式,不做數值轉換;有符號負數遇 %u,加 2 的 N 次方變正數」