對於 C/C++ 開發者而言,內存洩漏既常見又棘手。雖然智能指針、垃圾回收等技術層出不窮,但智能指針尚未普及且無法解決所有問題,而 C/C++ 領域的垃圾回收機制發展遲緩。現實是:內存洩漏始終是開發過程中必須面對的挑戰。好在現代工具鏈提供了有力的檢測手段,幫助我們定位洩漏源頭。
程序通過 malloc、realloc、new 從堆中分配任意大小的內存塊,使用完畢後必須顯式調用 free 或 delete 釋放。若某條執行路徑未釋放,該內存塊便無法再被使用,即發生堆內存洩漏。
void MyFunction(int nSize) {
char* p = new char[nSize];
if (!GetStringFrom(p, nSize)) {
MessageBox("Error");
return; // 此處未釋放 p,導致洩漏
}
// ... 使用 p
delete p;
}
上例中,若 GetStringFrom 返回 false,函數提前返回,delete p 不會執行,造成洩漏。這是典型的「入口分配、出口釋放」模式失效的情形。
內存洩漏不僅限於堆內存,還包括核心態 HANDLE、GDI Object、SOCKET、COM Interface 等系統資源。這些對象同樣消耗內存(甚至核心態內存),洩漏嚴重時會導致系統不穩定,危害性遠超普通堆內存洩漏。
void CMyView::OnPaint(CDC* pDC) {
CBitmap bmp;
CBitmap* pOldBmp;
bmp.LoadBitmap(IDB_MYBMP);
pOldBmp = pDC->SelectObject(&bmp);
// ...
if (Something()) {
return; // 未將 pOldBmp 選回,導致 HBITMAP 洩漏
}
pDC->SelectObject(pOldBmp);
}
長時間運行可能耗盡 GDI 堆,尤其在 Win9x 系統上更易暴露。
Something() 恒為真)。Something() 偶爾返回真)。測試環境對檢測至關重要。class ConnectionManager {
list _connlist;
public:
void OnClientConnected(SOCKET s) {
_connlist.push_back(new Connection(s));
}
void OnClientDisconnected(Connection* pconn) {
_connlist.remove(pconn);
delete pconn; // 若斷開時未調用此函數,Connection 對象滯留,形成隱式洩漏
}
~ConnectionManager() {
for (auto it : _connlist) delete it; // 程序結束時才釋放
}
};
內存洩漏本身不直接傷害用戶體驗,但其堆積終將耗盡系統資源。一次性洩漏相對無害,隱式洩漏最隱蔽也最危險。掌握分類特徵,結合靜態分析工具(如 Valgrind、Visual Studio 診斷工具)與代碼審查,是構建穩健 C/C++ 系統的關鍵。