C 語言如同一把鋒利的雕刻刀,在專家手中能創造高效優雅的程式,但稍有不慎即會傷及使用者。本文系統梳理 C 語言在詞法、語法、連結、語意、庫函數、預處理器與可移植性七大面向的經典陷阱,並提供避坑指南。
= 與 == 混淆C 語言用 = 表賦值、== 表比較。因賦值頻率較高,故採用較短符號。但這導致 if (x = y) 實為賦值後判斷非零,而非比較。
對策:若確需在條件中賦值,寫成 if ((x = y) != 0) 明確表達意圖;開啟編譯器警告(如 -Wparentheses)。
&/| 與 &&/|| 混用位元運算子 &、| 與邏輯運算子 &&、|| 優先級、短路求值行為迥異,誤用極易產生難以重現的 Bug。
編譯器採「最長匹配」原則:/* 一律視為註釋開頭,無視上下文。需注意 // 行註釋在 C99 才標準化,舊編譯器不支援。
如 int *p[10] 是指標陣列而非陣列指標。建議使用 typedef 或 cdecl 工具輔助閱讀。
如 & 優先級低於 ==,if (x & 1 == 0) 實為 if (x & (1 == 0))。習慣加括號明確分組。
if (x > 0); foo(); 導致 foo() 無條件執行。良好編碼風格要求單行區塊也加大括號。
switch 貫穿缺少 break 會導致案例貫穿。C 語言不支援隱式中斷,需显式處理。
else函式引數求值順序未定義;else 永遠匹配最近的未配對 if,建議永遠使用大括號。
C 編譯器不檢查跨檔案的外部符號型別一致性,extern int foo; 在另一檔定義為 float foo; 也能通過編譯,連結後行為未定義。解決:統一在標頭檔宣告,#include 共享。
f(i++) + g(i++) 結果不可預期。&&、||、! 保證左到右短路,可用於安全檢查 if (p && p->valid)。a[10] 合法索引 0–9,越界行為未定義。sizeof(ptr) 回傳指標大小而非陣列長度。NULL 是位址 0,"" 是指向 '\0' 的指標。getc() 回傳 int 而非 char,需用 int 接收以區分 EOF。stdout 預設行緩衝,交互式程式需 fflush() 或 setvbuf()。MAX(i++, j++) 導致 i 增加兩次)。#define INT_PTR int* 會使 INT_PTR a, b; 宣告 int *a, b;(b 為 int)。| 議題 | 差異 | 建議 |
|---|---|---|
| 識別碼長度 | 舊標準僅保證 6 字元外部、31 字元內部 | 使用足夠長度、避免前綴衝突 |
int 位寬 | 16/32/64 位皆合法 | 用 <stdint.h> 固定寬度型別 |
char 有號性 | 實作定義 | 明確使用 signed char/unsigned char |
| 右移位符號擴展 | 算術/邏輯右移視實作而定 | 避免依賴負數右移行為 |
| 整數除法捨入 | C99 截向零,舊標準允許向下捨入 | C99 後可依賴截向零 |
rand() 範圍 | 僅保證 0–32767 | 自行縮放或用 <random> (C++11) |
| 大小寫轉換 | 僅保證 ASCII 字母 | 多國語言環境用 <wctype.h> |