内存泄漏就是new出来的内存没有通过delete合理的释放。
重载new和delete检测内存泄漏原理是:
在重载的new中记录内存分配情况,在重载的delete中删除内存分配记录,从而跟踪所有内存分配信息。
下面代码的是在《Think in C++ V2》第2章中源码的例子基础上修改的。
一点笔记来自《Think in C++ V2》中第2章:
1. 断言是一个肯定语句,用来肯定显示设计意图。断言的意图是验证设计决定,造成它失败的唯一原因应该是程序逻辑有缺陷。
2. 如果想开启或关闭程序中某些位置的断言,使用#define或#undef NDEBUG。
在编译器选项中定义NDEBUG在release版本中关闭assert,使用命令g++ -D NDEBUG assertTest.cpp。
3. 代码重构,重构是通过改造系统内部的代码从而改进程序的设计,并且不改变程序的行为。
关于下面源码1调试中的一点经验与注意:
1. 在void* operator new(size_t size, const char* file, long line) 中,第一个参数size会被自动填充;
2. 使用重载的new,类的构造函数与析构函数也会被自动调用;
3. 对于new重载的使用,参考之前的总结
《new的理解》和
《C++智能指针auto_ptr》;
4. 在内存信息记录时使用的是数组memMap,使用链表List效率会更高(参考源码2);
5. 详见代码注释;代码还不够完善,有时间继续重构。
//源码1
#include <cassert>
#include <vector>
#include <iostream>
using namespace std;
//INFO日志
#define log_info(...) do {\
printf("<%s %s> %s %s:%d INFO:",__DATE__, __TIME__,__FUNCTION__,__FILE__,__LINE__)&&\
printf(__VA_ARGS__)&&\
printf("\n");\
}while(0)
//ERROR日志
#define log_error(...) do {\
printf("<%s %s> %s %s:%d ERROR:",__DATE__, __TIME__,__FUNCTION__,__FILE__,__LINE__)&&\
printf(__VA_ARGS__)&&\
printf("\n");\
}while(0)
#define INVALID -1
//内存跟踪与日志开关
bool traceFlag = false;
bool memFlag = false;
#define TRACE_ON() traceFlag = true
#define TRACE_OFF() traceFlag = false
#define MEM_ON() memFlag = true
#define MEM_OFF() memFlag = false
//内存跟踪信息表
struct memInfo
{
void* ptr;
const char* file;
long line;
};
//最大跟踪内存数
const int MAX_PTRS = 100;
//内存跟踪数组
memInfo memMap[MAX_PTRS] = {0};
//内存跟踪游标
int nPtrs = 0;
//(添加信息)内存跟踪信息表
void addPtr(void* p, const char* f, long l)
{
memMap[nPtrs].ptr = p;
memMap[nPtrs].file = f;
memMap[nPtrs].line = l;
++nPtrs;
}
//(查找信息)内存跟踪信息表
int findPtr(void* p)
{
for(size_t i = 0; i < nPtrs; ++i)
{
if(memMap[i].ptr == p)
return i;
}
return INVALID;
}
//(删除信息)内存跟踪信息表
void delPtr(void* p)
{
int pos = findPtr(p);
assert(pos >= 0);
for(size_t i = pos; i < nPtrs-1; ++i)
{
//当删除一个数据,后面的数据依此向前填充
memMap[i] = memMap[i+1];
}
//移动数据后,最后一个位置置为0
memMap[nPtrs-1].ptr = 0;
memMap[nPtrs-1].file = 0;
memMap[nPtrs-1].line = 0;
--nPtrs;
}
//(输出信息)内存跟踪信息表
void memInfo_output()
{
for(int i = 0; i < nPtrs; ++i)
{
log_info("memMap[%d].ptr= %p", i, memMap[i].ptr);
}
}
//重载new
void* operator new(size_t size, const char* file, long line)
{
//不管是否跟踪内存,都会malloc内存;相当于不使用重载new的情况
void* p = malloc(size);
if (p==NULL)
{
log_error("memory allocate failed");
exit(1);
}
if(memFlag)
{
if(nPtrs == MAX_PTRS)
{
log_error("memory map too small (increase MAX_PTRS)");
free(p);//内存跟踪失败,释放
exit(1);
}
addPtr(p, file, line);
}
if(traceFlag)
{
log_info("Allocated %u bytes at address %p (Located file: %s, line: %ld)", size, p, file, line);
}
return p;
}
// 重载new数组
void* operator new[](size_t size, const char* file, long line)
{
return operator new(size, file, line);
}
//宏定义new,在后续调用new时,使用new(__FILE__, __LINE__)
//void* operator new(size_t size, const char* file, long line) 中的第一个参数会被自动填充
#define new new(__FILE__, __LINE__)
//没有测试过带参数的delete重载
//#define delete delete(__FILE__, __LINE__)
//重载delete,可以调试下带参数的delete
//void operator delete(void* p, ***)
void operator delete(void* p)
{
//不管memFlag是否打开,都会释放内存;就是说,不管跟踪内存与否,都会正常释放内存
free(p);
if(memFlag)
{
if(findPtr(p) >= 0)
{
assert(nPtrs > 0);
delPtr(p);
}
//在内存跟踪信息表中找不到,要么为NULL指针,要么为unknow
else if(!p)
{
log_error("Delete NULL pointer: %p", p);
return;
}
else
{
log_error("Attempt to delete Unknown pointer: %p", p);
p = NULL;
return;
}
}
if(traceFlag)
log_info("Deleted memory at address %p", p);
//p = NULL;free(p) does not change the value of p itself, hence it still points to the same (now invalid) location
//值为空,free(p) 不会改变指针p的值,p仍然指向相同(非法)的地址。
p = NULL;
}
// Override array delete
void operator delete[](void* p)
{
operator delete(p);
} ///:~
//fooTest类用于测试局部变量自动释放内存
class fooTest
{
char* s;
public:
fooTest(const char*s )
{
this->s = new char[strlen(s) + 1];
strcpy(this->s, s);
}
~fooTest()
{
delete [] s;
}
};
//myTest类用于测试在调用重载new的情况下,也会自动调用构造函数与析构函数
class myTest
{
public:
myTest()
{
cout<<"Construct my test..."<<endl;
}
~myTest()
{
cout<<"Destruct my test..."<<endl;
}
};
// Sentinel相当于内存守卫,全局的Sentinel对象将在程序退出前检测内存泄露情况
struct memGuard
{
~memGuard()
{
if(memFlag)
{
if(nPtrs > 0)
{
for(size_t i = 0; i < nPtrs; ++i)
log_error("Leaked memory at: %p (file: %s, line %ld)", memMap[i].ptr, memMap[i].file, memMap[i].line);
}
else
log_info("No memory leaks!");
}
}
};
memGuard mG;
void test()
{
//如果有undef new,没有undef delete,就不会调用重载的new,而调用重载的delete,内存跟踪会出错。
//#undef new
//#undef delete
int* iPtr = new int;
int* aPtr = new int[3];
char * str = new char[9];
memInfo_output();
delete iPtr;
//delete iPtr; 重复调用delete,程序crash
delete [] aPtr;
//delete[] str;
delete str;
//delete NULL 指针
int* nPtr = NULL;
delete nPtr;
// 即使使用重载new,也会调用构造函数与析构函数
myTest* mtPtr = new myTest;
delete mtPtr;
//vector 这里不会调到重载的new
vector<int> v;
v.push_back(1);
//将调用2次重载new
fooTest *ftPtr = new fooTest("goodbye");
//内存泄露
//delete ftPtr;
//自动释放内存
fooTest ft("goodbye");
}
int main()
{
MEM_ON();
TRACE_ON();
test();
//在这里不能够OFF,因为全局变量“析构”在此范围之外。
//MEM_OFF();
//TRACE_OFF();
}
//源码2
//下面的源码来源于网络,仅供参考
#include<iostream>
using namespace std;
//---------------------------------------------------------------
// 内存记录
//---------------------------------------------------------------
class MemInfo {
private:
void* ptr;
const char* file;
unsigned int line;
MemInfo* link;
friend class MemStack;
};
//---------------------------------------------------------------
// 内存记录栈
//---------------------------------------------------------------
class MemStack {
private:
MemInfo* head;
public:
MemStack():head(NULL) { }
~MemStack() {
MemInfo* tmp;
while(head != NULL) {
free(head->ptr); // 释放泄漏的内存
tmp = head->link;
free(head);
head = tmp;
}
}
void Insert(void* ptr, const char* file, unsigned int line) {
MemInfo* node = (MemInfo*)malloc(sizeof(MemInfo));
node->ptr = ptr; node->file = file; node->line=line;
node->link = head; head = node;
}
void Delete(void* ptr) {
MemInfo* node = head;
MemInfo* pre = NULL;
while(node != NULL && node->ptr!=ptr) {
pre = node;
node = node->link;
}
if(node == NULL)
cout << "删除一个没有开辟的内存" << endl;
else {
if(pre == NULL) // 删除的是head
head = node->link;
else
pre->link = node->link;
free(node);
}
}
void Print() {
if(head == NULL) {
cout << "内存都释放掉了" << endl;
return;
}
cout << "有内存泄露出现" << endl;
MemInfo* node = head;
while(node != NULL) {
cout << "文件名: " << node->file << " , " << "行数: " << node->line << " , "
<< "地址: " << node->ptr << endl;
node = node->link;
}
}
};
//---------------------------------------------------------------
// 全局对象 mem_stack记录开辟的内存
//---------------------------------------------------------------
MemStack mem_stack;
//---------------------------------------------------------------
// 重载new,new[],delete,delete[]
//---------------------------------------------------------------
void* operator new(size_t size, const char* file, unsigned int line) {
void* ptr = malloc(size);
mem_stack.Insert(ptr, file, line);
return ptr;
}
void* operator new[](size_t size, const char* file, unsigned int line) {
return operator new(size, file, line); // 不能用new
}
void operator delete(void* ptr) {
free(ptr);
mem_stack.Delete(ptr);
}
void operator delete[](void* ptr) {
operator delete(ptr);
}
//---------------------------------------------------------------
// 使用宏将带测试代码中的new和delte替换为重载的new和delete
//---------------------------------------------------------------
#define new new(__FILE__,__LINE__)
//---------------------------------------------------------------
// 待测试代码
//---------------------------------------------------------------
void bad_code() {
int *p = new int;
char *q = new char[5];
delete []q;
}
void good_code() {
int *p = new int;
char *q = new char[5];
delete p;
delete []q;
}
//---------------------------------------------------------------
// 测试过程
//---------------------------------------------------------------
int main() {
good_code();
bad_code();
mem_stack.Print();
system("PAUSE");
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)