C++ 將記憶體配置與釋放的控制權交給程式設計師,靈活之餘也埋下「記憶體洩漏」的隱憂。新手常忘記 delete 對應的 new,小程式或許無傷大雅,但 24/7 運行的伺服器程式若洩漏,系統記憶體將迅速耗盡導致崩潰。掌握偵測與預防技巧,是合格 C++ 開發者的必修課。
洩漏往往在特定條件下才觸發,從海量程式碼中人工排查極其困難,尤其在多執行緒環境更是噩夢。本文介紹 Visual Studio 2003 (VC6 需微調) 內建 CRT 偵錯堆的偵測方法。
假設程式每 10 分鐘洩漏量相近,記為 L。觀察三個時間點 Tn-2、Tn-1、Tn (間隔 10 分鐘),總配置量 A = N(正常) + L(洩漏)。正常配置 N 會不斷釋放而總量穩定,洩漏 L 只增不減。單位時間洩漏量 dL = An - An-1。定期產生記憶體快照並比較差異,即可定位洩漏程式碼位置。
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC // 讓 dump 包含原始碼行號
#include <stdlib.h>
#include <crtdbg.h>
#endif
_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
HANDLE hLogFile = CreateFile("./log/memleak.log", GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, hLogFile);
_CrtMemState s1, s2, s3;
_CrtDumpMemoryLeaks(); // 輸出目前所有未釋放區塊
_CrtMemCheckpoint(&s2);
if (_CrtMemDifference(&s3, &s1, &s2)) {
_CrtMemDumpStatistics(&s3); // 輸出相鄰時間點差異
_CrtMemCheckpoint(&s1); // 更新基準點
}
time_t now = time(0);
struct tm *nowTime = localtime(&now);
_RPT4(_CRT_WARN, "%02d %02d:%02d:%02d snapshot dump.\n",
nowTime->tm_mday, nowTime->tm_hour, nowTime->tm_min, nowTime->tm_sec);
建議將上述程式碼封裝為函式,由計時器定期呼叫或手動觸發。
Dump 內容包含未釋放區塊的配置呼叫堆疊與原始碼位置 (如 d:\xxx\worker.cpp(903))。人工比對不切實際,需撰寫小工具自動化:
掌握此技巧,記憶體洩漏不再是無法捕捉的幽靈!