記憶體洩漏(memory leak)
想像你租了一些寄物櫃,而每隔一陣子就忘記把某把鑰匙放哪了。櫃子還鎖著、仍以你的名義保留著,但你再也打不開、也還不掉。租夠多櫃子、丟夠多鑰匙,整間設施最終就會塞滿你用不到的房間。記憶體洩漏正是如此:你的程式配置了、卻再也搆不著、且永遠不會釋放的堆積記憶體,於是它毫無用處地被保留著,直到程式結束。
精確地說:當指向某堆積區塊的最後一個指標在區塊未被釋放的情況下遺失時,就發生洩漏。該區塊仍被配置著——配置器仍認為它在使用中——但你程式裡沒有任何變數還持有它的位址,因此根本無從釋放它。常見的發生方式:在釋放舊配置前就用新配置覆寫了唯一的指標(連續兩次 p = malloc(...) 之間沒有 free);在函式的錯誤處理路徑上提早返回而跳過了 free;或在重建資料結構時弄丟了指標。和當機不同,洩漏是無聲的——當下不會出任何錯。程式只是慢慢用掉越來越多記憶體。
它有多嚴重取決於程式跑多久。一個洩漏一點點然後就結束的短命命令列工具幾乎不成問題,因為作業系統會在行程結束時回收它的所有記憶體。但一個長時間執行的程式——伺服器、常駐程式、開了好幾天的桌面應用——若每個請求都洩漏一點,就會無界限地成長、因置換而變慢,最終在耗盡記憶體時被殺掉。Valgrind 的 memcheck 與 AddressSanitizer 的洩漏偵測器之所以存在,正是為了找出那筆少了對應 free 的配置。
char *p = malloc(100); p = malloc(200); /* 洩漏:第一塊 100 位元組區塊現在再也搆不著 */ /* ... */ free(p); /* 只釋放了那塊 200 位元組區塊 */
在釋放前就覆寫唯一的指標,會讓第一塊永遠洩漏;後面的 free 搆不到它。
洩漏不是當機,在短時間執行下可能通過每一項測試。它只咬長壽的程式;行程結束時作業系統會釋放一切,這可能掩蓋掉工具程式中的洩漏。