使用CRT调试内存分配堆来找出未释放的内存空间
忘记释放已经分配的内存是一种常见的编程错误,当然我指的是在C++编程当中,例如下面的代码里面就存在一个忘记释放内存的编程错误。我个人觉得忘记释放内存的编程错误是不可避免的,毕竟程序员都是人,困了,心情不好了,代码过于复杂啦等等都可能导致忘记加上一句delete XXX语句。
#include <tchar.h> #include <windows.h> #include <string> #include <iostream> using namespace std; class CTestClass { public: CTestClass(LPWSTR szName) { m_lpName = new wstring(szName); } ~CTestClass() { } void PrintName() { wcout << *m_lpName << endl; } private: wstring *m_lpName; }; HRESULT CreateTestClass(LPWSTR szName, CTestClass **ppObject) { *ppObject = new CTestClass(szName); if ((*ppObject) == NULL) return E_FAIL; else return S_OK; } int _tmain(int argc, _TCHAR* argv[]) { CTestClass *pObject = NULL; HRESULT hr = CreateTestClass(L"This is a Test", &pObject); if (hr != S_OK) { return -1; } else { pObject->PrintName(); // pObject没有被释放 return 0; } }
实际上Visual Studio已经提供了方法帮助你快速找到这些没有释放的内存。
Visual Studio提供了一系列的CRT调试API,CRT提供了一个调试内存分配堆,可以跟踪和管理内存在什么地方分配,当你在这个堆上分配内存的时候,每一次内存分配调用例如malloc或者new,CRT都会额外分配大约36个字节用来保存例如这个内存块分配的文件名、行号、内存块的大小等信息,最后CRT将这些内存块使用一个双链表链接起来。每一次内存释放的时候,free或者delete函数就从这个内存块链表里面将要释放的内存块删除,因此在需要检查内存泄漏的时候,只要遍历这个双链表依次打印出这些内存块就可以发现所有未释放的内存了。下面是CRT内存块的原始声明:
typedef struct _CrtMemBlockHeader { // Pointer to the block allocated just before this one: struct _CrtMemBlockHeader *pBlockHeaderNext; // Pointer to the block allocated just after this one: struct _CrtMemBlockHeader *pBlockHeaderPrev; char *szFileName; // File name int nLine; // Line number size_t nDataSize; // Size of user block int nBlockUse; // Type of block long lRequest; // Allocation number // Buffer just before (lower than) the user's memory: unsigned char gap[nNoMansLandSize]; } _CrtMemBlockHeader;
下面的代码演示了如何使用CRT提供的调试API来修改刚才的源文件检测未释放的内存空间(注意红色添加的部分):
#include <tchar.h> #include <windows.h> #include <string> #include <iostream> // 使用CRT调试API #include <crtdbg.h> using namespace std; // 将所有的内存分配函数new替换成CRT提供的调试new #ifdef _DEBUG #define DEBUG_CLIENTBLOCK new(_CLIENT_BLOCK, __FILE__, __LINE__) #else #define DEBUG_CLIENTBLOCK #endif #ifdef _DEBUG #define new DEBUG_CLIENTBLOCK #endif class CTestClass { public: CTestClass(LPWSTR szName) { m_lpName = new wstring(szName); } ~CTestClass() { } void PrintName() { wcout << *m_lpName << endl; } private: wstring *m_lpName; }; HRESULT CreateTestClass(LPWSTR szName, CTestClass **ppObject) { *ppObject = new CTestClass(szName); if ((*ppObject) == NULL) return E_FAIL; else return S_OK; } int _tmain(int argc, _TCHAR* argv[]) { // 设置CRT调试API的报表输出模式,将所有的错误、警告还有断言都输出到控制台 _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT); _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT); _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT); CTestClass *pObject = NULL; HRESULT hr = CreateTestClass(L"This is a Test", &pObject); if (hr != S_OK) { return -1; } else { pObject->PrintName(); // 检查未释放的内存 _CrtDumpMemoryLeaks(); return 0; } }