指標賦予 C 語言直接記憶體操作能力,但也引入一類難以重現、難以定位的錯誤。以下整理七大高發陷阱與防禦模式。
成因:malloc/calloc/realloc 分配堆積記憶體,無對應 free。
防禦:
• 分配與釋放配對,所有權清晰 (誰分配誰釋放)。
• 使用 valgrind --leak-check=full 例行檢測。
• 考慮 RAII 風格封裝或智慧指標 (C++)。
成因:非法寫入 (越界、使用後釋放、未初始化指標) 未即時崩潰,破壞鄰近資料結構,後續無關處崩潰。
典型案例:結構體柔性陣列成員寫入超出分配大小,後續新增成員時編譯器報錯「無法定義變數」,實為前序污染。
防禦:
• 開啟 -fsanitize=address (ASan) 編譯測試。
• 結構體末尾加 金絲雀值 定期校驗。
• 嚴格檢查緩衝區邊界。
錯誤:if (*str == '\0') 先解參考再判斷 → 若 str == NULL 直接崩潰。
正確:if (str == NULL || *str == '\0') 先判斷指標值,再解參考。
案例:char str[3] = "abc"; // 實際需 4 字元含 '\0'
防禦:
• 字串緩衝區預留 +1 空間給結尾 NUL。
• 使用 strncpy/snprintf 限制長度。
• 靜態分析工具 (cppcheck, clang-tidy) 捕獲編譯期可見越界。
現象:char *p = "sdfg"; p++; printf("%s", p); // 輸出 "dfg"
連續 p++ 移動指標,最終指向 '\0' 或越界。
防禦:
• 保留原始指標 char *start = p; 供後續釋放/重用。
• 使用索引 p[i] 而非移動指標。
錯誤:
char *get_str() {
char str[] = "abcd"; // 棧上
return str; // 返回棧位址 → 野指標
}
正確:static char str[] = ... (線程不安全)。void get_str(char *buf, size_t len)。return strdup("abcd"); (呼叫者負責 free)。
錯誤:
free(p); if (p != NULL) free(p); // p 未置 NULL,二次 free → 未定義行為正確模式:
if (p) { free(p); p = NULL; }
原則:free 後立即置 NULL,杜絕野指標與重複釋放。
| 陷阱 | 核心對策 |
|---|---|
| 洩漏 | 所有權明確 + valgrind/ASan 例行檢測 |
| 污染 | ASan + 金絲雀值 + 邊界檢查 |
| 空指標解參考 | 先判 NULL 再解參考 |
| 越界 | 預留 NUL + 安全函式 + 靜態分析 |
| 疊加 | 保留基址 / 改用索引 |
| 棧逃逸 | 呼叫者分配 / 靜態 / 動態分配 |
| 重複釋放 | free 後立即 p = NULL |