while (1) {
fprintf(stdout, "stdout."); // 行緩衝:遇 \n 或滿才刷新
fprintf(stderr, "stderr."); // 無緩衝:立即輸出
sleep(1);
}
// 輸出:stderr.stderr.stderr.... (好久才出現 stdout.)
修正:setbuf(stdout, NULL); 將 stdout 設為無緩衝,或每次 fflush(stdout); 手動刷新。
原理:終端關聯時 stdout 默認行緩衝,stderr 默認無緩衝(保證錯誤信息即時可見)。重定向到文件時 stdout 變全緩衝,更明顯。
char* str = "我"; // UTF-8 編碼佔 3 字節
printf("%d\n", printf(str)); // 輸出 3,而非 1
鐵律:printf 返回實際輸出的字節數,而非字符數、碼位數。UTF-8 下中文字符佔 3 字節,返回 3 完全正確。
switch (a) {
int b = 2; // 永遠不會執行!
case 1: printf("%d", b); break;
}
標準規定:case/default 標籤前的代碼**永遠不會執行**(編譯器直接生成跳轉指令)。b 未初始化,讀取行為未定義。
對策:嚴禁在 switch 塊頂層聲明帶初始化的變量,需放入 case 內部或用 {} 包裹成複合語句。
int i = 1;
printf("%zu\n", sizeof(i++)); // 輸出 4 (int 大小)
printf("%d\n", i); // 輸出 1,i 未增!
核心規則:sizeof 是**編譯期運算符**(除非常數長度數組),僅根據表達式**類型**計算大小,**不求值**表達式。i++ 只提供類型信息 int,副作用被丟棄。
char ch = 2["hello"]; // ch == 'l'
原理:a[b] 由編譯器統一翻譯為 *(a + b)。
2["hello"] → *(2 + "hello") → *("hello" + 2) → "hello"[2]。
本質:指針算術 ptr + index 與加法交換律,index[ptr] 只是冷門寫法。
int d = 2;
printf("%f\n", d); // 輸出垃圾值如 0.000000 或隨機浮點數
原因:printf 根據 %f 從棧上讀取 sizeof(double) (8字節) 數據並按 IEEE 754 解釋。實際傳入 int (4字節),高 4 字節為棧上殘留垃圾,組合成無意義浮點數。
嚴重後果:棧對齊差異、調用約定差異可能導致後續參數全亂,屬**未定義行為**。
typedef union {
float f;
struct { unsigned int dot:23; unsigned int exp:8; unsigned int sign:1; } bits;
} float_value;
float_value fv;
fv.bits.sign = 1; // 負數
fv.bits.exp = 128; // 指數偏移 127 → 實際指數 1
fv.bits.dot = 0; // 尾數 1.0
printf("%f\n", fv.f); // 輸出 -2.000000
| 現象 | 底層機制 | 避坑口訣 |
|---|---|---|
| 輸出順序亂 | stdout 行緩衝 vs stderr 無緩衝 | 交互輸出加 fflush 或 setbuf(NULL) |
| printf 返回字節數 | 格式化輸出實際寫入字節計數 | 字符數≠字節數,UTF-8 中文 3 字節 |
| switch 變量未初始化 | case 標籤即跳轉地址,前置代碼不可達 | switch 頂層不聲明帶初始化變量 |
| sizeof 不求值 | 編譯期按類型計算大小 | sizeof 內不寫副作用表達式 |
| 數組下標可交換 | a[b] ≡ *(a+b) ≡ b[a] | 冷知識,代碼中別這樣寫 |
| 格式化字符串類型不匹配 | 變參無類型檢查,按格式字符串盲目讀棧 | 開啟 -Wformat 警告,嚴格匹配 |