干货!简单代码实现内存泄露自动监测!建议收藏

前言

在平时的项目开发过程中,经常遇到内存泄露问题。虽然我们可以借助valgrind工具进行内存使用问题检测,但是这个工具毕竟不是太好用。

那么,有没有什么简单方便的办法可以自动记录哪个内存泄露,以及这个内存申请时所处的代码行呢?

实现思路

以C++为例,C++的内存申请方式有两种,malloc和new。

  • new/delete 是C++的操作符,需要编译器支持。它调用的分别为operator new()和operator delete()。
  • malloc/free 是C的标准库函数,需要头文件支持。有2个头文件都可以使用,C头文件, .调用时 malloc(x);C++头文件, 注意没有后缀名.调用时要写 std::malloc(x) 注意std前缀。

备注:详细可以参考前面的文章《new与malloc的特点及运行原理的区别》


我们下面通过替换malloc/free函数,并重写类的new/delete操作符,并生成内存日志文件的形式进行检测。

设计思路如下:

①申请空间时,新建以当前新地址命名的内存日志文件,malloc操作时还可以在文件中记录当前代码所在行数。

②释放空间时,查找并删除以此地址命名的内存日志文件。扩展:如果找不到文件说明重复释放。

③程序运行完毕后,查看是否有未删除的内存日志文件。如果存在,则说明有内存泄露。

注意:operator new 与 operator delete 和 operator new[] 和 operator delete[] 应该成对重载,这四个函数为默认static函数,static可写可不写,编译器默认会给声明成static inline类型。


范例代码

// memory_check.cpp : 内存检查
// 替换malloc和new是为了检查内存泄漏和溢出。虽然有valgrind但是并不好用。
//

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
using namespace std;

#define DEBUG 1 //测试宏,临时定义并打开

//宏定义需要放头文件中
//支持c++11的编译器使用constexpr代替宏
#ifdef DEBUG
	#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900)
		//C++11 is supported
		template<typename T>
		constexpr auto malloc_debug(T size) { return _malloc(size, __FILE__, __LINE__); }

		template<typename T>
		constexpr auto free_debug(T ptr) { return _free(ptr, __FILE__, __LINE__); }
	#else
		//C++11 is not supported
		#define malloc_debug(size)  _malloc(size,__FILE__,__LINE__); 
		#define free_debug(ptr)		_free(ptr,__FILE__,__LINE__); 
	#endif
#else
	#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900)
		//C++11 is supported
		template<typename T>
		constexpr auto malloc_debug(T size) { return malloc(size); }

		template<typename T>
		constexpr auto free_debug(T ptr) { return free(ptr); }
	#else
		//C++11 is not supported
		#define malloc_debug(size) malloc(size)
		#define free_debug(ptr) free(ptr)
	#endif
#endif // DEBUG


void* _malloc(size_t size, const char* filename, int line)
{
cout << "my malloc." << endl;

void* p = malloc(size);

char fname[128] = { 0 };
sprintf_s(fname, "%p.mem_malloc", p);

FILE* fp;
int ret = fopen_s(&fp, fname, "a");//最好使用fopen_s,使用fopen提示“not safe”
if (ret == 0)//文件打开成功
{
fprintf(fp, "[+] %s: %d line, addr:%p size:%zd \n", filename, line, p, size);
fflush(fp);
fclose(fp);
}
return p;
}

void _free(void* ptr, const char* filename, int line)
{
cout << "my free." << endl;

char fname[128] = { 0 };
sprintf_s(fname, "%p.mem_malloc", ptr);

if (_unlink(fname) < 0)
{
printf(" double free !  %s: %d line, addr:%p \n", filename, line, ptr);
}

free(ptr);
}


class A
{
public:
A() {};
~A() {};

int x = 0;

#ifdef DEBUG
public:
//static 可写可不写,编译器将 operator new 重载函数默认为 static inline
//在重载函数中做自己的内存管理
static void* operator new(size_t size)
{
cout << "my operator new." << endl;
void* ptr = ::operator new(size);

char fname[128] = { 0 };
sprintf_s(fname, "%p.mem_new", ptr);

FILE* fp;
int ret = fopen_s(&fp, fname, "w");//最好使用fopen_s,使用fopen提示“not safe”
if (ret == 0)//文件打开成功
{
fprintf(fp, "[+] addr:%p size:%zd \n", ptr, size);
fflush(fp);
fclose(fp);
}

return ptr;
}

//size_t 参数可选
static void operator delete(void* ptr)
{
cout << "my operator delete." << endl;

char fname[128] = { 0 };
sprintf_s(fname, "%p.mem_new", ptr);

if (_unlink(fname) < 0)
{
cout << " double free ! addr:" << ptr << endl;
}

return ::operator delete(ptr);
}

void* operator new[](size_t size)
{
cout << "my operator new[]." << endl;

void* ptr = ::operator new[](size);

char fname[128] = { 0 };
sprintf_s(fname, "%p.mem_new_array", ptr);

FILE* fp;
int ret = fopen_s(&fp, fname, "w");//最好使用fopen_s,使用fopen提示“not safe”
if (ret == 0)//文件打开成功
{
fprintf(fp, "[+] addr:%p new[%zd] \n", ptr, size);
fflush(fp);
fclose(fp);
}

return ptr;

}

void operator delete[](void* ptr)
{
cout << "my operator delete[]." << endl;

char fname[128] = { 0 };
sprintf_s(fname, "%p.mem_new_array", ptr);

if (_unlink(fname) < 0)
{
cout << " double free ! addr:" << ptr << endl;
}

return ::operator delete[](ptr);
}
#endif

};


int main_memory_test(void)
{
void* p1 = malloc_debug(3);
free_debug(p1);

A* p2 = new A();
delete p2;

A* p_arry = new A[5];
delete[] p_arry;

return 0;
}


int main()
{
    std::cout << "Hello World!"<<endl << endl;

    main_memory_test();
}

执行效果

(测试工具vs2022)

malloc_debug执行后的效果:

new执行后的效果:

new[]执行后的效果:

由此可见:

当前IDE在new[]时,多分配了8个字节的cookie用于存储需要执行几次构造函数。

返回给用户的指针的初始地址是cookie后的地址。

delete[]时,自动向前获取8个字节的cookie信息,用于确定需要执行几次析构函数。

内存正确被释放后,内存日志文件以前全部被自动删除:


原创不易,欢迎点赞、关注、转发、收藏!

原文链接:,转发请注明来源!