C 語言雖歷久彌新,但其細節陷阱常讓開發者踩坑。本文精選 18 個經典問題,涵蓋指標運算、記憶體管理、字串處理、編譯器行為等核心主題,並給出標準解答與實務建議,助你建立扎實的 C 語言功力。
char *p = malloc(10); // 編譯錯誤:初始化器非常數
解析:靜態/全域變數的地址必須在編譯期確定,malloc 在執行期分配記憶體,故非法。建議:改用巨集定義大小,或在函式內部(自動變數)初始化。
*p++ 運算子優先序*p++ 等價 *(p++):先取值再遞增指標。若要遞增指向的值,寫 (*p)++ 或 ++*p。建議:善用括號消除歧義,提升可讀性。
((int *)p)++; // 錯誤:轉換結果為右值,不可遞增
正確做法:p = (char *)((int *)p + 1); 或 p += sizeof(int);。GCC 會依指標型別自動調整位移。
空指標(NULL)保證不指向任何物件;未初始化指標值不定,可能指向任意位址。鐵律:宣告指標即初始化為 NULL。
在指標語境(初始化、指派、比較、有原型的固定參數函式呼叫)中,0 自動轉為空指標。但變參函式或無原型函式呼叫時,必須強制轉型:(char *)0。建議:統一使用 NULL(Linux 定義為 (void *)0)。
arr vs &arrarr:退化為指向第一個元素的指標(T *)。&arr:指向整個陣列的指標(T (*)[N]),sizeof(&arr) == sizeof(arr),但 &arr + 1 跳過整個陣列。真正的陣列指標:int (*ap)[N];。通常只需指向元素的指標(int *p = arr;),除非操作「陣列的陣列」。
二維陣列退化為「陣列指標」而非「指標的指標」。函式簽名應為 void f(int a[][NCOLS]) 或 void f(int (*ap)[NCOLS]),第二維度不可省略。
strcat 緩衝區溢位風險char *s1 = "Hello, "; // 指向唯讀區,且空間不足
char *s2 = "world!";
strcat(s1, s2); // 未定義行為
正確: char s1[20] = "Hello, "; 確保目的緩衝區可寫且足夠大。
回傳指標必須指向:靜態緩衝區、呼叫者提供的緩衝區、或 malloc 分配的記憶體。絕不可回傳區域自動陣列的位址(函式返回後記憶體失效)。
free 後作業系統記憶體佔用未下降大多數 malloc/free 實作(如 glibc ptmalloc)將釋放的記憶體保留於進程堆中供後續複用,不立即歸還 OS。
calloc vs malloccalloc(m,n) 相當於 malloc(m*n) + memset(0)。零填充不保證產生有效空指標或浮點數 0.0。free 可安全釋放 calloc 記憶體。
sizeof('a') 在 C 與 C++ 差異C 語言字元常數型別為 int,故 sizeof('a') == sizeof(int)(通常 4)。C++ 中為 char(1 位元組)。
extern int f(struct x *p); 的作用域陷阱原型中首次出現的結構標籤作用域僅限該原型。解決:在原型前加 struct x; 前向宣告,建立檔案作用域的不完整型別。
const int n = 5; int a[n]; 非法C 語言 const 僅表「唯讀」,非編譯期常數。陣列維度需編譯期常數,請用 #define 或 enum。
main 必須回傳 int標準規定 int main(void) 或 int main(int argc, char *argv[])。void main() 不合規,雖 GCC 允許但不可攜。
#pragma 指令提供編譯器專屬擴展(原始碼列控制、結構對齊、警告抑制等),不可攜但實用。
#pragma once非標準擴展,等同 #ifndef 保護,但可攜性較差。常見參數:message, pack, warning, region 等。
深入理解這 18 個問題,能有效避開 C 語言常見陷阱,寫出更健壯、可攜的程式碼。建議配合編譯器警告(-Wall -Wextra -pedantic)與靜態分析工具持續改善程式品質。