自制内存泄漏检测工具

之前偶然发现,CRT检测内存泄漏的代码其实并不是特别复杂,就是用一个链表记录所有申请出来的内存,然后在程序退出时检测还有哪些内存未被释放。于是有了自制内存泄漏检测工具的想法。基本上是参考这篇文章和CRT源码写出来的:

C++不用工具,如何检测内存泄漏? - 知乎 (zhihu.com)

代码写的非常简陋,并没有经过实战检验,估计还有很多bug,但用来理解检测内存泄漏的原理已经大致够用了。另外,不可用于MFC程序,如果类重载了new运算符也不行,这个问题暂时解决不了。

链接:https://pan.baidu.com/s/15cqif7sZiN1P8HGacPofhQ 
提取码:kqa7 

第1步:定义双向链表,链表节点为clMemHeader对象,用来记录分配过的内存

typedef struct clMemHeader
{
	struct clMemHeader*	pBlockHeaderNext;
	struct clMemHeader*	pBlockHeaderPrev;
	const char *        szFileName;
	int                 nLine;
	size_t              nDataSize;
} clMemHeader;

clMemHeader* pFirstHead = NULL;
clMemHeader* pLastHead = NULL;

第2步:重载new、new[]运算符:

void* operator new(size_t nSize, const char* lpszFileName, int nLine)
{
	return alloc_mem(nSize, lpszFileName, nLine);
}

void* operator new[](size_t nSize, const char* lpszFileName, int nLine)
{
	return alloc_mem(nSize, lpszFileName, nLine);
}

为了避免new里面又调用new导致循环递归,实际分配内存使用malloc,封装成函数alloc_mem。为了记录分配的内存,将其添加到全局链表中:

void* alloc_mem(size_t nSize, const char* lpszFileName, int nLine)
{
	size_t nTotalSize = sizeof(clMemHeader) + nSize;
	clMemHeader* pHead = (clMemHeader*)malloc(nTotalSize);

	// 与CRT类似,向左增长,但注意链表头在最左边(CRT就这么写的,有点别扭)
	if (pFirstHead)
	{
		pFirstHead->pBlockHeaderPrev = pHead;
	}
	else
	{
		pLastHead = pHead;
	}
	
	pHead->pBlockHeaderNext = pFirstHead;
	pHead->pBlockHeaderPrev = NULL;
	pHead->szFileName = lpszFileName;
	pHead->nLine = nLine;
	pHead->nDataSize = nSize;

	// 链表头始终在最左边
	pFirstHead = pHead;

	void* pRet = ((clMemHeader*)pHead) + 1;
	return pRet;
}

第3步:重载delete运算符

void operator delete(void* p)
{
	free_mem(p);
}

// 与new参数列表要一致,否则报错C4291
void operator delete(void* p, const char* lpszFileName, int nLine)
{
	free_mem(p);
}

同理,为了避免循环递归,释放内存用free,封装成函数free_mem。调用delete的时候将内存指针从全局链表中删掉:

void free_mem(void* pUserData)
{
	clMemHeader* pHead = (clMemHeader*)((char*)pUserData - sizeof(clMemHeader));
	
	// 从链表中删除节点
	if (pHead->pBlockHeaderNext)
	{
		pHead->pBlockHeaderNext->pBlockHeaderPrev = pHead->pBlockHeaderPrev;
	}
	else
	{
		pLastHead = pHead->pBlockHeaderPrev;
	}

	if (pHead->pBlockHeaderPrev)
	{
		pHead->pBlockHeaderPrev->pBlockHeaderNext = pHead->pBlockHeaderNext;
	}
	else
	{
		pFirstHead = pHead->pBlockHeaderPrev;
	}

	// 释放
	free(pHead);
}

第4步:检测内存泄漏

void _dumpMemLeak()
{
	OutputDebugStringA("*************************************\r\n");
	OutputDebugStringA("Detect memory leaks!\r\n");

	char buf[1024] = {0};

	for (clMemHeader* pHead = pFirstHead; pHead != NULL; pHead = pHead->pBlockHeaderNext)
	{
		memset(buf, 0, 1024);
		sprintf_s(buf, 1024, "%s(%d) leak %d byte at %p. \r\n", 
			pHead->szFileName, pHead->nLine, pHead->nDataSize, ((clMemHeader*)pHead + 1));

		OutputDebugStringA(buf);
	}

	OutputDebugStringA("*************************************\r\n");
}

在程序退出之前调用_dumpMemLeak,检测全部链表中节点的数量,如果节点数量不为0则有内存泄漏,将泄漏的内存打印出来。

实际使用时需要先定义宏:

// 强制让所有new都走重载的运算符
#define new new(__FILE__, __LINE__)

下面是测试代码,看看能否找出内存泄漏:

int main()
{
	char* pBuf = new char[1024];
	//delete[] pBuf;

	// 无需在new中显式调用构造函数,编译器会自动生成调用构造函数的代码
	CPerson* pPerson1 = new CPerson;
	delete pPerson1;
	pPerson1 = NULL;

	CPerson* pPerson2 = new CPerson(100);
	delete pPerson2;
	pPerson2 = NULL;

	// 编译器会自动计算实际需要的内存大小,并传给new[]
	stPerson* pPersonArray = new stPerson[10];
	//delete[] pPersonArray;

	// 检测内存泄漏
	_dumpMemLeak();
}

CPerson的定义,如果调用无参构造函数,不会泄漏,调用有参构造函数,则会内存泄漏:

class CPerson
{
public:
	CPerson()
	{
		int a = 0;
	}

	CPerson(int nBufSize)
	{
		char* pBuf = new char[nBufSize];
	}
};

stPerson的定义:

struct stPerson
{
	int nID;
};

测试结果:

可以看到,内存泄漏的代码位置已经打印出来了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值