文章目录
-
- [vs2019 - detected memory leak](#vs2019 - detected memory leak)
- 概述
- 笔记
- [vs2019 console](#vs2019 console)
- [vs2019 MFC Dlg](#vs2019 MFC Dlg)
- 但是,工程大了之后,VS2019提示的就变了样
- 整好的内存泄漏侦测头文件和实现
- my_debug_new_define.h
- my_debug_new_define.cpp
- 在所有.cpp文件入口处包含my_debug_new_define.h
- [包含的细节 - 如果有预编译头文件(e.g. pch.h), 必须包含在pch.h后面](#包含的细节 - 如果有预编译头文件(e.g. pch.h), 必须包含在pch.h后面)
- [包含的细节 - 如果工程中的.cpp头部已经重定向了new, 要在重定向之后包含my_debug_new_define.h](#包含的细节 - 如果工程中的.cpp头部已经重定向了new, 要在重定向之后包含my_debug_new_define.h)
- 如果看到了内存泄漏,却无法定位到具体代码行
- 在工程中加入调试开始和结束函数
- END
vs2019 - detected memory leak
概述
用VS2019建立的控制台工程, 在调试模式下, 如果出了内存泄漏,是没有提示的。
// 网上的大佬在2010年就给出了解决方法
// \ref https://www.codeproject.com/articles/66324/detecting-memory-leaks-using-the-crtdbg-library
笔记
分别在新建的VS2019 console和MFC Dlg程序中试试, 出现内存泄漏时,让VS2019IDE将内存泄漏点报出来,且能转到具体代码行。
vs2019 console
c
// exp003_vs2019_console_detect_memory_leak.cpp
#include <iostream>
#include <crtdbg.h>
#define new new(_CLIENT_BLOCK,__FILE__, __LINE__)
// 新建vs2019控制台工程, 如果有内存泄漏, 默认是不提示的。
// 网上的大佬在2010年就给出了解决方法
// \ref https://www.codeproject.com/articles/66324/detecting-memory-leaks-using-the-crtdbg-library
int main()
{
_CrtMemState s1, s2, s3;
_CrtMemCheckpoint(&s1);// Memory snapshot will be taken at this point
std::cout << "Hello World!\n";
uint8_t* pBuf = new uint8_t[100];
_CrtMemCheckpoint(&s2);// Another Memory snapshot will be taken at this point
// Memory difference which has been allocated but deallocted between s1 and s2
// Memory check points will be calculated.
if (_CrtMemDifference(&s3, &s2, &s1))
{
_CrtDumpMemoryLeaks(); //Dumps the memory leak in Debugger Window, if any, between s1 and s2 memeory check points.
}
return 0;
}
vs2019 MFC Dlg
新建后的工程,如果发生内存泄漏,也是有提示的。且能定位到具体代码行。
但是,工程大了之后,VS2019提示的就变了样
如果不是新建的工程,而是已经写了一段时间的工程,代码行数上来之后,在debug版的调试模式下,程序结束后,确实能看到IDE提示有内存泄漏。但是,无法直接定位到泄漏点。 这点,在以前工作中遇到的工程中,也遇到过。如果工程大了,出现了内存泄漏,就不那么好找。
工程大了,VS2019IDE大概率都定位不到具体哪些行引起的内存泄漏。
我在查资料之前,自己用笨办法搞定了。用排除法。让执行一个特定操作可以引起内存泄漏,那么就在这个模块中再用排除法去查。
不过,这得是自己写的工程。如果是同事维护的工程或者开源工程,用排除法基本没得搞。
这时该如何排查呢(是否可以让VS2019自动定位到内存泄漏点?)?我就是因为这个场景,才去查如何定位内存泄漏,才找到了网上大佬2010年就提出的解决方法。
但是,网上大佬的方法只适合工程没有hook new的场景。
e.g. MFC向导生成的工程,已经在debug模式下, 将new换成了DEBUG_NEW
c
// Memory tracking allocation
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)
这时,如果再使用crtdbg.h中的new重定向方法,直接编译不过,和MFC的重定向new是冲突的。只能是使用MFC重定向的new
这时再使用_CrtDumpMemoryLeaks,效果和不加是一样的(只能看到有内存泄漏,无法知道内存泄漏精确代码行)
观察了一下发现,在MFC工程中生成的.cpp顶部,都自己重新重定向了new
c
// MoneyCostParser.cpp: 定义应用程序的类行为。
//
#include "pch.h"
#include "framework.h"
#include "MoneyCostParser.h"
#include "MoneyCostParserDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW // !!!
#endif
c
// MoneyCostParserDlg.cpp: 实现文件
#include "pch.h"
#include "framework.h"
#include "MoneyCostParser.h"
#include "MoneyCostParserDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW // !!!
#endif
再查看以前用MFC向导生成的类中的.cpp
c
// MyWorkerThread.cpp: implementation of the CMyWrokerThread class.
//
//
#include "pch.h"
#include <process.h>
#include "CMyWrokerThread.h"
#ifdef _DEBUG
#undef THIS_FILE // !!!
static char THIS_FILE[] = __FILE__; // !!!
#define new DEBUG_NEW // !!!
#endif
再看自己工程,只能看到有内存泄漏,但是不知道具体泄漏行的场景。
最后用手工排除法确定了.cpp, 这个cpp是自己手写的。
最后试了一下,只要在非MFC生成的.cpp(包括自己手写的.cpp,或者是第三方的.cpp)顶部,加上重定向new的代码,就可以实现自动定位内存泄漏行。
如果在VS2019下,看到有内存泄漏的提示,但是无法定位到精确代码行,这时就要检查一下,是否每个.cpp的开头都有new的重定向代码。
如果是想让自己写的.cpp适用于控制台和MFC或者其他平台,就需要include一个头文件(这个头文件不包含条件包含的保护 )进来。
然后按照各种平台的编译环境,将new换成debug new.
整好的内存泄漏侦测头文件和实现
my_debug_new_define.h
c
//! \file my_debug_new_define.h
// 这个头文件是new的debug版定义, 要包含到每个需要的.cpp中,不能有头文件多重包含的保护
// my_debug_new_define.h 只能包含到.cpp中,不能包含到其他.h中
#define PROG_TYPE_CONSOLE 1
#define PROG_TYPE_MFC 2
#if defined(_MFC_VER)
#define MY_NEW_TYPE PROG_TYPE_MFC
#elif defined(_CONSOLE)
#define MY_NEW_TYPE PROG_TYPE_CONSOLE
#else
#error !!! unknown env, please fixed the code for detected memory leak
#endif
// \ref https://learn.microsoft.com/zh-cn/cpp/preprocessor/hash-if-hash-elif-hash-else-and-hash-endif-directives-c-cpp?view=msvc-170
#if (defined MY_NEW_TYPE) && (PROG_TYPE_CONSOLE == MY_NEW_TYPE)
#include <crtdbg.h>
#ifdef new
#undef new
#endif
#define new new(_CLIENT_BLOCK,__FILE__, __LINE__)
#elif (defined MY_NEW_TYPE) && (PROG_TYPE_MFC == MY_NEW_TYPE)
#ifdef _DEBUG
#ifdef new
#undef new
#endif
#define new DEBUG_NEW
#endif
#endif
void new_debug_begin();
void new_debug_end();
my_debug_new_define.cpp
c
//! \file my_debug_new_define.cpp
#include "pch.h"
#include "my_debug_new_define.h"
#if (defined MY_NEW_TYPE) && (PROG_TYPE_CONSOLE == MY_NEW_TYPE)
static _CrtMemState s1;
static _CrtMemState s2;
static _CrtMemState s3;
void new_debug_begin()
{
_CrtMemCheckpoint(&s1);// Memory snapshot will be taken at this point
}
void new_debug_end()
{
_CrtMemCheckpoint(&s2);// Another Memory snapshot will be taken at this point
// Memory difference which has been allocated but deallocted between s1 and s2
// Memory check points will be calculated.
if (_CrtMemDifference(&s3, &s2, &s1))
{
_CrtDumpMemoryLeaks(); //Dumps the memory leak in Debugger Window, if any, between s1 and s2 memeory check points.
}
}
#else
void new_debug_begin()
{
}
void new_debug_end()
{
}
#endif
在所有.cpp文件入口处包含my_debug_new_define.h
包含的细节 - 如果有预编译头文件(e.g. pch.h), 必须包含在pch.h后面
c
//! \file CTest.cpp
//! \note 用VS2019类向导添加的非UI类,是没有DEBUG_NEW定义的
#include "pch.h" // 如果工程中有预编译头文件(e.g. pch.h), 那么.cpp中要首先包含pch.h
#include "my_debug_new_define.h" // <== 如果要包含头文件,都要在pch.h的后面
#include "CTest.h"
int CTest::fn_add(int a, int b)
{
char* p = new char[0x100]; // 模拟内存泄漏
return (a + b);
}
包含的细节 - 如果工程中的.cpp头部已经重定向了new, 要在重定向之后包含my_debug_new_define.h
c
// exp005MfcDlg.cpp: 定义应用程序的类行为。
//
#include "pch.h"
#include "framework.h"
#include "exp005MfcDlg.h"
#include "exp005MfcDlgDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
#include "my_debug_new_define.h" // 如果向导生成的代码中已经重定向了new, 需要放到该代码后面,以便重定向new到crtdbg
// Cexp005MfcDlgApp
BEGIN_MESSAGE_MAP(Cexp005MfcDlgApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
如果看到了内存泄漏,却无法定位到具体代码行
检查一下,是否工程中所有的.cpp头部都包含了my_debug_new_define.h
在工程中加入调试开始和结束函数
c
// exp005Vs2019Console.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "CTest.h"
#include "my_debug_new_define.h"
int main()
{
new_debug_begin(); // debug new begin
char* p = new char[66];
CTest obj;
obj.fn_add(1, 2);
std::cout << "Hello World!\n";
new_debug_end(); // debug new end
}