Windows平台下的内存泄漏检测
在C/C++中内存泄漏是一个不可避免的问题,很多新手甚至有许多老手也会犯这样的错误,下面说明一下在windows平台下如何检测内存泄漏。
在windows平台下内存泄漏检测的原理大致如下。
1. 在分配内存的同时将内存块的信息保存到相应的结构中,标识为已分配
2. 当内存释放时在结构中查找,并将相应的标识设置为已释放
3. 在需要的位置调用HeapWalk,遍历整个堆内存,找到对应的内存块的首地址,并与定义的结构中的数据相匹配,根据结构中的标识判断是否释放,未释放的话给出相应的提示信息。
另外在VS系列的编译器中如果输出的调试信息的格式为:文件名(行号)双击这样的输出信息,会自动跳转到对应的位置,利用这点可以很容易的定位到未释放的内存的位置。
为了实现上述功能,我们使用重载new和delete的方式。下面是具体的代码:
#define MAX_BUFFER_SIZE 1000
typedef struct tag_ST_BLOCK_INFO
{
TCHAR m_szSourcePath[MAX_PATH];
INT m_iLine;
BOOL m_bDelete;
void *pBlock;
}ST_BLOCK_INFO, *LP_ST_BLOCK_INFO;
class CMemoryLeak
{
public:
CMemoryLeak(void);
~CMemoryLeak(void);
void MemoryLeak();
void add(LPCTSTR m_szSourcePath, INT m_iLine, void *pBlock);
int GetLength();
ST_BLOCK_INFO& operator [](int nSite);
protected:
HANDLE m_heap;//自定义堆
LP_ST_BLOCK_INFO m_pBlockInfo;
int m_BlockSize; //当前缓冲区大小
int m_hasInfo;//当前记录了多少值
};
CMemoryLeak::CMemoryLeak(void)
{
if (m_heap == NULL)
{
//打开异常检测
m_heap = HeapCreate(HEAP_GENERATE_EXCEPTIONS,0,0);
ULONG HeapFragValue = 2;
//允许系统记录堆内存的使用
HeapSetInformation( m_heap,HeapCompatibilityInformation,&HeapFragValue ,sizeof(HeapFragValue)) ;
}
if (NULL == m_pBlockInfo)
{
m_pBlockInfo = (LP_ST_BLOCK_INFO)HeapAlloc(m_heap, HEAP_ZERO_MEMORY, MAX_BUFFER_SIZE * sizeof(ST_BLOCK_INFO));
m_BlockSize = MAX_BUFFER_SIZE;
m_hasInfo = 0;
}
}
void CMemoryLeak::add(LPCTSTR m_szSourcePath, INT m_iLine, void *pBlock)
{
//当前缓冲区已满
if (m_hasInfo >= m_BlockSize)
{
//扩大缓冲区容量
HeapReAlloc(m_heap, HEAP_ZERO_MEMORY, m_pBlockInfo, m_BlockSize * 2 * sizeof(ST_BLOCK_INFO));
m_BlockSize *= 2;
}
m_pBlockInfo[m_hasInfo].m_bDelete = FALSE;
m_pBlockInfo[m_hasInfo].m_iLine = m_iLine;
_tcscpy(m_pBlockInfo[m_hasInfo].m_szSourcePath, m_szSourcePath);
m_pBlockInfo[m_hasInfo].pBlock = pBlock;
m_hasInfo++;
}
CMemoryLeak::~CMemoryLeak(void)
{
HeapFree(m_heap, 0, m_pBlockInfo);
HeapDestroy(m_heap);
}
void CMemoryLeak::MemoryLeak()
{
TCHAR pszOutPutInfo[2*MAX_PATH]; //调试字符串
BOOL bRecord = FALSE; //当前内存是否被记录
PROCESS_HEAP_ENTRY phe = {};
HeapLock(GetProcessHeap()); //检测时锁定堆防止对堆内存进行写入
OutputDebugString(_T("开始检查内存泄露情况.........\n"));
while (HeapWalk(GetProcessHeap(), &phe))
{
//当这块内存正在使用时
if( PROCESS_HEAP_ENTRY_BUSY & phe.wFlags )
{
bRecord = FALSE;
for(UINT i = 0; i < m_hasInfo; i ++ )
{
if( phe.lpData == m_pBlockInfo[i].pBlock)
{
//校验这块内存是否被释放
if(!m_pBlockInfo[i].m_bDelete)
{
StringCchPrintf(pszOutPutInfo,2*MAX_PATH,_T("%s(%d):内存块(Point=0x%08X,Size=%u)\n")
,m_pBlockInfo[i].m_szSourcePath,m_pBlockInfo[i].m_iLine,phe.lpData,phe.cbData);
OutputDebugString(pszOutPutInfo);
}
bRecord = TRUE;
break;
}
}
if( !bRecord )
{
StringCchPrintf(pszOutPutInfo,2*MAX_PATH,_T("未记录的内存块(Point=0x%08X,Size=%u)\n")
,phe.lpData,phe.cbData);
OutputDebugString(pszOutPutInfo);
}
}
}
HeapUnlock(GetProcessHeap());
OutputDebugString(_T("内存泄露检查完毕.\n"));
}
int CMemoryLeak::GetLength()
{
return m_hasInfo;
}
ST_BLOCK_INFO& CMemoryLeak::operator [](int nSite)
{
return m_pBlockInfo[nSite];
}
CMemoryLeak g_MemoryLeak;
void* __cdecl operator new(size_t nSize,LPCTSTR pszCppFile,int iLine)
{
//在分配内存的时候将这块内存信息记录到相应的结构中
void *p = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nSize);
g_MemoryLeak.add(pszCppFile, iLine, p);
return p;
}
void __cdecl operator delete(void *p, TCHAR *pstrPath, int nLine)
{
::operator delete(p);
HeapFree(GetProcessHeap(), 0, p);
}
void __cdecl operator delete(void* p)
{
//依次遍历结构体数组,找到对应内存块的记录,将标志设置为已删除
for (int i = 0; i < g_MemoryLeak.GetLength(); i++)
{
if (p == g_MemoryLeak[i].pBlock)
{
g_MemoryLeak[i].m_bDelete = TRUE;
}
}
HeapFree(GetProcessHeap(), 0, p);
}
下面是一个测试的例子
#ifdef _UNICODE
//将__FILE__转化为对应的UNICODE版本
#define GRS_WIDEN2(x) L ## x
#define GRS_WIDEN(x) GRS_WIDEN2(x)
#define __WFILE__ GRS_WIDEN(__FILE__)
//这段代码不能与重载的申明在同一个头文件下,否则在编译时会将定义的new函数进行替换
#define new new(__WFILE__,__LINE__)
#define delete(p) ::operator delete(p,__WFILE__,__LINE__)
#else
#define new new(__FILE__,__LINE__)
#define delete(p) ::operator delete(p,__FILE__,__LINE__)
#endif
int _tmain()
{
int* pInt1 = new int;
int* pInt2 = new int;
float* pFloat1 = new float;
BYTE* pBt = new BYTE[100];
delete[] pBt;
//在DEBUG环境下启用检测
#ifdef _DEBUG
g_MemoryLeak.MemoryLeak();
#endif
return 0;
}
上面的代码中,定义了一个结构体 ST_BLOCK_INFO来保存每个分配的内存块的信息,同时采用数组的方式来保存多个内存块的信息,为了便于管理这些信息,专门定义了一个类来操作这个数组,类中记录了数组的首地址,当前保存的信息总量和当前能够容纳的信息总量,同时这个数组支持动态扩展。
在遍历时利用HeapWalk函数遍历系统默认堆中的所有内存,找到正在使用的内存,并在结构数组中查找判断内存是否被释放,如果未背释放则输出调试信息。在主函数中利用宏定义的方式,使程序只在debug环境下来校验内存泄漏,方便调试同时在发行时不会拖累程序运行。
最后对程序再做最后几点说明:
1. 动态数组不要使用new 和delete来分配和释放空间,因为我们重载了这两个函数,这样在检测的时候会有一定的影响
2. new本身的定义如下:
void* operator new(size_t size) throw(std::bad_alloc)
平时在使用上例如void p = new int 其实等于void *p = new(sizeof(int)),同时如果使用void *p = new int[10] 等于 void *p = new(sizeof(int) 10) 上面定义的#define new new(WFILE,LINE) 其实在调用时相当于void *p = new(WFILE,LINE) int,也就是等于void *p = new(sizeof(int), WFILE,LINE)当然delete也是同理
3. 在申请数组空间时不要使用系统默认的堆,因为重载new和delete使用的就是系统默认堆,检测的也是默认堆,如果用默认堆来保存数组数据,会对结果产生影响。
4. 当然用这样的方式写有点浪费内存资源,如果一个程序需要new出大量的数据,那么需要的额外内存也太多,所以可以使用链表来保存,当调用delete时将结点从链表中删除,这样只要链表中存在的都是未被删除的;或者使用数组,当有一个被删除,将这个位置的索引用队列的方式记录下来,每当要新增数组数据时根据队列中保存的索引找到对应的位置进行覆盖操作。这样可以节省一定的空间。
Windows平台下的内存泄漏检测的更多相关文章
- Windows 下的内存泄漏检测方法
在 Windows 下,可使用 Visual C++ 的 C Runtime Library(CRT) 检测内存泄漏. 首先,我们在.c或.cpp 文件首行插入这一段代码: #define _CRTD ...
- C的内存泄漏检测
一,Windows平台下的内存泄漏检测 检测是否存在内存泄漏问题 Windows平台下面Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法,原理大 ...
- 【转】Unix下C程序内存泄漏检测工具Valgrind安装与使用
Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Goo ...
- C++程序内存泄漏检测方法
一.前言 在Linux平台上有valgrind可以非常方便的帮助我们定位内存泄漏,因为Linux在开发领域的使用场景大多是跑服务器,再加上它的开源属性,相对而言,处理问题容易形成“统一”的标准.而在W ...
- Cocos开发中性能优化工具介绍之Visual Studio内存泄漏检测工具——Visual Leak Detector
那么在Windows下有什么好的内存泄漏检测工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检测功能,我们可以使用第三方工具Visual Leak Detector(以下简 ...
- C++内存泄漏检测工具
C++内存泄漏检测工具 1.VC自带的CRT:_CrtCheckMemory 调试器和 CRT 调试堆函数 1.1用法: /************************************ ...
- C++的内存泄漏检测
C++大量的手动分配.回收内存是存在风险的,也许一个函数中一小块内存泄漏被重复放大之后,最后掏空内存. 这里介绍一种在debug模式下测试内存泄漏的方法. 首先在文件的开头以确定的顺序写下这段代码: ...
- Windows平台下利用APM来做负载均衡方案 - 负载均衡(下)
概述 我们在上一篇Windows平台分布式架构实践 - 负载均衡中讨论了Windows平台下通过NLB(Network Load Balancer) 来实现网站的负载均衡,并且通过压力测试演示了它的效 ...
- windows平台下基于VisualStudio的Clang安装和配置
LLVM 是一个开源的编译器架构,它已经被成功应用到多个应用领域.Clang是 LLVM 的一个编译器前端,它目前支持 C, C++, Objective-C 以及 Objective-C++ 等编程 ...
随机推荐
- hdu 4883 区间选点
昨天比赛的时候没有做出来,本来是想用贪心的,可是贪了好久都没有招, 今天在网上搜了解题报告~好像说这是一类区间选点问题: 有一个好的做法: (1)首先把题目中的时间全转化为分钟,那么区间就在0-144 ...
- Github-karpathy/char-rnn代码详解
Github-karpathy/char-rnn代码详解 zoerywzhou@gmail.com http://www.cnblogs.com/swje/ 作者:Zhouwan 2016-1-10 ...
- eclipse中JPA插件的安装与使用
说明 上周实验室学习了数据库设计相关的内容,其中涉及到将数据库实体化的问题,JPA是一个很好的实现工具,便开始着手于JPA的学习.因为JPA涉及到的知识还是挺多的,需要学习许多新的知识,所以对于JPA ...
- Object.assign()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign 说明 Ob ...
- 9.python面向对象编程
面向对象的几个核心特性如下 Class 类一个类即是对一类拥有相同属性的对象的抽象.蓝图.原型.在类中定义了这些对象的都具备的属性(variables(data)).共同的方法 Object 对象 一 ...
- 教程:安装禅道zentao项目管理软件github上的开发版
该文章转自:吕滔博客 直接从github拉下来的禅道的源码,是跑不起来的.除非你按我的教程来做...哈哈哈(不要脸)~~~~ 禅道官网提供的版本包是带了有安装文件,并有打包合成一些css.js文件的. ...
- iOS UITabView简写瀑布流
代码demo 一.tabViewCell,通过image的比例高算出cell 的高度 #import "TableViewCell.h" @implementation Table ...
- 【Zookeeper】源码分析之服务器(三)之LeaderZooKeeperServer
一.前言 前面分析了ZooKeeperServer源码,由于QuorumZooKeeperServer的源码相对简单,于是直接分析LeaderZooKeeperServer. 二.LeaderZooK ...
- caffe CuDNN报错问题解决
解决cudnn问题:Loaded runtime CuDNN library: 5005 (compatibility version 5000) but source was compiled wi ...
- 2.sass变量、嵌套、混合(mixin)、继承拓展、@import、comment
变量.嵌套.混合(mixin).继承拓展.@import.comment 变量的意义 在sass里我们可以定义多个变量来存放颜色.边框等等的样式,这样就可以在下面想要使用样式的时候使用变量了 这样的优 ...