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++ 等编程 ...
随机推荐
- logstash 向elasticsearch写入数据,怎样指定多个数据template
之前在配置从logstash写数据到elasticsearch时,指定单个数据模板没有问题.可是在配置多个数据模板时候,总是不成功,后来找了非常多资料,最终找到解决的方法,就是要多加一个配置项: te ...
- GPU版的tensorflow在windows上的安装时的错误解决方案
1.用vs编译cuda的sample时会提示找不到"d3dx9.h"."d3dx10.h"."d3dx11.h"头文件的错误,如果没有安装这 ...
- Python爬虫(二十三)_selenium案例:动态模拟页面点击
本篇主要介绍使用selenium模拟点击下一页,更多内容请参考:Python学习指南 #-*- coding:utf-8 -*- import unittest from selenium impor ...
- javascript中的异步 macrotask 和 microtask 简介
javascript中的异步 macrotask 和 microtask 简介 什么是macrotask?什么是microtask?在理解什么是macrotask?什么是microtask之前,我们先 ...
- 修改Intellij IDEA中工程对应的Java SDK、Scala SDK
如果编译Scala工程时,遇到如下异常: can't expand macros compiled by previous versions of Scala 很可能是工程的scala版本,和依赖的包 ...
- Axure学习笔记(一)
Axture是一种快速制作原型的工具,在产品经理和体验设计师之中非常流行,不过现在产品经理比较难找,所以我只好上阵研究了一下. 经过几天的研究,看了小楼老师的一些视频,看了一些文档,做了 ...
- DataBase MongoDB高级知识
MongoDB高级知识 一.mongodb适合场景: 1.读写分离:MongoDB服务采用三节点副本集的高可用架构,三个数据节点位于不同的物理服务器上,自动同步数据.Primary和Secondary ...
- ES6之Proxy及Proxy内置方法
Proxy是ES6提供的代理器可以起到拦截作用,写法形式如 var proxy = new Proxy(target,handler);参数target表示要拦截的目标对象,handler是用来定制拦 ...
- 转:iOS开发之多种Cell高度自适应实现方案的UI流畅度分析
本篇博客的主题是关于UI操作流畅度优化的一篇博客,我们以TableView中填充多个根据内容自适应高度的Cell来作为本篇博客的使用场景.当然Cell高度的自适应网上的解决方案是铺天盖地呢,今天我们的 ...
- SLAM入门之视觉里程计(2):两视图对极约束 基础矩阵
在上篇相机模型中介绍了图像的成像过程,场景中的三维点通过"小孔"映射到二维的图像平面,可以使用下面公式描述: \[ x = MX \]其中,\(c\)是图像中的像点,\(M\)是一 ...