內存洩露是 C/C++ 程序最常見的嚴重缺陷。本文總結內存洩露的三大經典模式,並通過鏈表操作代碼逐一演示,幫助開發者建立敏銳的洩露嗅覺。
場景:遍歷刪除鏈表時,誤將中間節點的 next 置 NULL,導致後續節點與頭節點失去聯系,無法被後續釋放循環訪問到。
// 錯誤代碼片段
for (mytempnd = mylist->next; mytempnd; mytempnd = mytempnd->next) {
if (++j > 3) { mytempnd->next = NULL; break; } // 斷開鏈表
}
for (mytempnd = mylist->next; mytempnd; mytempnd = mytempnd->next) {
free(mytempnd); // 僅能釋放前 4 個節點,後 6 個成為垃圾
}
後果:後 6 個節點內存永久洩露,程序運行越久可用內存越少。
場景:同上文(第 48 篇),刪除部分鏈表節點但未更新前驅節點的 next,導致前驅指針指向已釋放內存。
隱患:後續誤解引用導致數據損壞、崩潰或安全漏洞。
場景:多個鏈表節點的 data 指向同一動態分配字符串,刪除其中一個節點時 free(data),其他節點的 data 瞬間變懸空。
對策:引用計數、深拷貝、明確所有權歸屬。
// 構建 10 節點鏈表
mynode *mylist = initlist();
mynode *mytempnd = mylist;
for (int i = 0; i < 10; i++) mytempnd = addnode(mytempnd, i, 20+i);
// 正確輸出
for (mytempnd = mylist->next; mytempnd; mytempnd = mytempnd->next)
printf("id:%d, age:%d\n", mytempnd->number, mytempnd->age);
// 正確銷毀:雙指針法,保存 next 再 free 當前
for (mytempnd = mylist->next; mytempnd; ) {
mynode *next = mytempnd->next;
free(mytempnd);
mytempnd = next;
}
free(mylist);
malloc/calloc/realloc 必須有對應的 free,且只能 free 一次。next)時,確保不會切斷對其他有效內存的唯一訪問路徑。free(p); p = NULL; 防懸空指針二次傷害。-fsanitize=address,運行時用 Valgrind/ASan 定位洩露點。正常銷毀輸出 10 行 delete id:0..9;垃圾模式僅輸出前 4 行刪除信息,後 6 節點靜默洩露;懸空/共享模式程序雖能運行但埋下定時炸彈。
內存洩露本質是內存塊失去所有可達引用(垃圾)或引用指向無效內存(懸空/共享錯誤)。養成「分配即規劃釋放路徑、修改指針即檢查可達性、共享即建立協議」的編程習慣,配合靜態分析與動態檢測工具,是寫出健壯 C 代碼的必經之路。