在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平台下的内存泄漏检测的更多相关文章

  1. Windows 下的内存泄漏检测方法

    在 Windows 下,可使用 Visual C++ 的 C Runtime Library(CRT) 检测内存泄漏. 首先,我们在.c或.cpp 文件首行插入这一段代码: #define _CRTD ...

  2. C的内存泄漏检测

    一,Windows平台下的内存泄漏检测 检测是否存在内存泄漏问题 Windows平台下面Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法,原理大 ...

  3. 【转】Unix下C程序内存泄漏检测工具Valgrind安装与使用

    Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Goo ...

  4. C++程序内存泄漏检测方法

    一.前言 在Linux平台上有valgrind可以非常方便的帮助我们定位内存泄漏,因为Linux在开发领域的使用场景大多是跑服务器,再加上它的开源属性,相对而言,处理问题容易形成“统一”的标准.而在W ...

  5. Cocos开发中性能优化工具介绍之Visual Studio内存泄漏检测工具——Visual Leak Detector

    那么在Windows下有什么好的内存泄漏检测工具呢?微软提供Visual Studio开发工具本身没有什么太好的内存泄漏检测功能,我们可以使用第三方工具Visual Leak Detector(以下简 ...

  6. C++内存泄漏检测工具

    C++内存泄漏检测工具 1.VC自带的CRT:_CrtCheckMemory   调试器和 CRT 调试堆函数 1.1用法: /************************************ ...

  7. C++的内存泄漏检测

    C++大量的手动分配.回收内存是存在风险的,也许一个函数中一小块内存泄漏被重复放大之后,最后掏空内存. 这里介绍一种在debug模式下测试内存泄漏的方法. 首先在文件的开头以确定的顺序写下这段代码: ...

  8. Windows平台下利用APM来做负载均衡方案 - 负载均衡(下)

    概述 我们在上一篇Windows平台分布式架构实践 - 负载均衡中讨论了Windows平台下通过NLB(Network Load Balancer) 来实现网站的负载均衡,并且通过压力测试演示了它的效 ...

  9. windows平台下基于VisualStudio的Clang安装和配置

    LLVM 是一个开源的编译器架构,它已经被成功应用到多个应用领域.Clang是 LLVM 的一个编译器前端,它目前支持 C, C++, Objective-C 以及 Objective-C++ 等编程 ...

随机推荐

  1. Spark修炼之道(进阶篇)——Spark入门到精通:第九节 Spark SQL执行流程解析

    1.总体执行流程 使用下列代码对SparkSQL流程进行分析.让大家明确LogicalPlan的几种状态,理解SparkSQL总体执行流程 // sc is an existing SparkCont ...

  2. BZOJ 1211 HNOI2004 树的计数 Prufer序列

    题目大意:给定一棵树中全部点的度数,求有多少种可能的树 Prufer序列.详细參考[HNOI2008]明明的烦恼 直接乘会爆long long,所以先把每一个数分解质因数.把质因数的次数相加相减.然后 ...

  3. POJ 2367 topological_sort

    Genealogical tree Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 2920 Accepted: 1962 Spe ...

  4. 一、OpenStack入门 之 初步认识

    OpenStack入门 之 初步认识 写在前面 从 OpenStack 基础知识開始学起,剖析 OpenStack 架构.分析 OpenStack 的各个组件的功能.原理和用法,通过实战演练来掌握 O ...

  5. C++ 虚指针、成员变量与类对象的偏移地址

    先给出一段代码实现 #include <iostream> using namespace std; class animal { protected: int age; public: ...

  6. linux shell 推断文件或目录是否真的存在

    #推断文件或目录是否存在 filepath=/data/test.txt folderpath=/data/qtech #推断文件是否存在 if [ -f "$file" ] th ...

  7. Java集合源代码剖析(二)【HashMap、Hashtable】

    HashMap源代码剖析 ; // 最大容量(必须是2的幂且小于2的30次方.传入容量过大将被这个值替换) static final int MAXIMUM_CAPACITY = 1 << ...

  8. 商城项目回顾整理(二)easyUi数据表格使用

    后台主页: 商品的数据表格展示 引入用户表数据表格展示 引入日志表数据表格展示 引入订单表数据表格展示 后台主页代码: <%@ page language="java" co ...

  9. iOS动态性:动态添加属性的方法——关联(e.g. 向Category添加属性)

    想到要如何为所有的对象增加实例变量吗?我们知道,使用Category可以很方便地为现有的类增加方法,但却无法直接增加实例变量.不过从Mac OS X v10.6开始,系统提供了Associative ...

  10. 解决WebService/WCF调用时报错"服务器提交了协议冲突. Section=ResponseStatusLine"问题

    今天更新了一个网站,新增了一个页面,调用WebService,在测试环境好好的,部署到正式环境后就莫名报错: 服务器提交了协议冲突. Section=ResponseStatusLine 网上查了好多 ...