語法與語義類
| 錯誤 | 錯誤代碼 | 正確代碼 | 說明 |
| 大小寫混淆 | printf("%d", A); | printf("%d", a); | C 區分大小寫 |
| 實數求餘 | float a,b; a%b; | int a,b; a%b; | % 僅限整數 |
| 字符/字串混淆 | char c; c="a"; | char c; c='a'; | 單引號字符,雙引號字串 |
| = 與 == 混用 | if (a=3) | if (a==3) | = 賦值,== 比較 |
| 漏分號 | a=1 b=2; | a=1; b=2; | 語句必須以 ; 結尾 |
| 多餘分號 | if (a>0); { ... } | if (a>0) { ... } | 分號導致空語句 |
| scanf 缺 & | scanf("%d", a); | scanf("%d", &a); | 需傳遞位址 |
| scanf 格式不匹配 | scanf("%d,%d", &a,&b); 輸入 "3 4" | 輸入 "3,4" 或改格式 "%d%d" | 非格式字元需原樣輸入 |
| %c 讀取空白 | scanf("%c%c%c", &a,&b,&c); 輸入 "a b c" | scanf(" %c %c %c", ...) | %c 讀取空白,加空白跳過 |
| printf 格式與類型不符 | float a; printf("%d", a); | printf("%f", a); | 編譯不報錯,運行結果錯誤 |
| scanf 限定精度 | scanf("%7.2f", &a); | scanf("%f", &a); | 輸入不可指定精度 |
| switch 缺 break | case 1: ... case 2: ... | case 1: ... break; | 貫穿導致邏輯錯誤 |
| while vs do-while | while(cond) vs do{}while(cond) | 視需求選擇 | 前者先判斷,後者先執行 |
| 陣列大小用變數 | int n; scanf("%d",&n); int a[n]; | #define N 100; int a[N]; | C89 不支援 VLA,C99 支援但不推薦 |
| 下標越界 | int a[10]; a[10]=0; | a[9]=0; | 下標 0~size-1 |
| 字串 scanf 加 & | scanf("%s", &str); | scanf("%s", str); | 陣列名即首址 |
| 形參與區域變數同名 | int f(int x){ int x; } | int f(int x){ int y; } | 形參在函數體外宣告 |
記憶體與指標類
- 未分配記憶體即 strcpy:
char *p; strcpy(p, "hello"); → 崩潰。正確:char buf[100]; strcpy(buf, "hello"); 或 malloc。
- 字串常數修改:
char *s="AAA"; s[0]='B'; → 未定義行為。正確:char s[]="AAA"; 或 const char *s="AAA";。
- free 後繼續使用:釋放後指標值未變,但指向記憶體已不可用。習慣
free(p); p=NULL;。
- 函數傳回區域陣列指標:區域陣列在棧上,函數返回後失效。改用靜態、動態分配或輸出參數。
前處理與巨集陷阱
#define SQUARE(x) ((x)*(x))
int a=5, b=SQUARE(a++); // b = (6)*(7) = 42,非 25!a 遞增兩次
// 正確:使用 inline 函數或確保參數無副作用
浮點數比較
// 錯誤
if (x == 0.0) ...
// 正確
if (fabs(x) < 1e-6) ...
總結建議
- 開啟編譯器警告:
gcc -Wall -Wextra -pedantic。
- 善用靜態分析工具。
- 養成「先想再寫、寫完再查」習慣。
- 閱讀經典書籍:《C 陷阱與缺陷》、《C 專家編程》。