C++重载new,以及实现检测内存泄露版本的new

在c++中,new作为一个操作符,也是可以被重载的,这个可能很多人比较陌生。在 Effective C++这本书中,专门提到了这方面的知识,看过此书,做一些总结,顺便在网上找到一些内容,实现一个可以检测内存泄露的内存分配机制(new delete)。


1. new_handler

在铺叙重载new之前,先说一下new_handler, 如果读过 windows结构化异常,对这个机制应该并不陌生,在windows结构化异常中,当出现未捕捉的异常出现时候,有一个函数handler用于处理这种默认情况,同时这个函数是可以替换成自己写的版本。


new_handler也是一样,当调用new的时候,不能成功分配内存的时候,就会调用new_handler

声明形式如下: 


namespace std

{

      typedef void (*new_handler)();

      new_handler set_new_handler(new_handler p) thow();

}

其中new_handler就是一个返回值和参数都是空的函数,set_new_handler()设置一个新的handler的同时,返回旧版本的handler。


当new分配空间失败的时候,就会调用这个函数,就好像求助的感觉一样,既然有求助的意味,所以new_handler一般要实现如下一些功能: 

1. 让更多内存被使用, 即有可以释放内存的功能

2. 安装另一个new_handler,如果当前的handler无法得到更多内存,则要考虑更换handler,使得在下次调用的时候,用新的handler来处理内存释放。

3. 卸除new_handler,也就是将null传递给 set_new_handler方法,如果当前没有handler,那么new调用失败会抛出异常

4. 不返回,通常调用 abort, exit.


2. 重载new

首先说一下重载new的必要性,

1. 用来检测运用上的错误,例如内存泄露,多次删除一个指针等,最后我们将给出一个实现检测内存泄露的new

2. 强化效能。因为自带的new函数是一个行为比较中庸的函数,对于大内存,小内存,多线程都考虑。所以自然效率会低一些,所以我们可以根据自己的需求制定new,例如在完全单线程的情况下不用考虑线程安全的问题。

3. 为了收集使用上的统计数据。在实现一个真正使用自己程序的new和delete之前,应该搜集一些程序使用动态内存的特点,例如大小,寿命等。这个时候可以重载new 来实现这些功能。

2.1 全局重载new

      这个提法主要是为了和class new 做区分而用的。

      假设现在要实现这样功能的new,如果要分配内存是 size,实际分配内存要比size大两个字节,然后分别在分配好的内存前后多四个字节,存储一个特殊的内容,作为签名。 在delete的时候,可以检测者 多分配的,以判断程序运行期间,是否对对这段内存的前后进行非法访问(underruns 和 overruns)

     

#include "Signew.h"




const int signiture = 0xDEADBEEF;

typedef unsigned char Byte;

void *operator new(std::size_t size) throw (std::bad_alloc) //重载 new,当没有内存可以分配的时候,抛出bad_alloc异常
{
	using namespace std;
	size_t realSize = size + 2 * sizeof(int); //比实际size 多分配内存,作为签名所用
	void *pMem = malloc(realSize);

	if(!pMem) throw bad_alloc();

	*(static_cast<int*>(pMem)) = signiture; //在之前存储签名
	*(reinterpret_cast<int*>(static_cast<Byte*>(pMem) + realSize - sizeof(int))) = signiture; //在之后存储签名,要做指针类型转换,实现 +- 

	return static_cast<Byte*>(pMem) + sizeof(int); //返回实际可用内存


}

2.2 类(class)new

      当然可以专门为class实现一个new操作,而不是用全局的new来实例化一个对象。

      下面的实现就是一个例子: 

#ifndef WIDGET_H
#define WIDGET_H

#include <new>

namespace Test
{

	class Widget
	{
	private:
		int sx,sy;
		int ex,ey;
	public:

		Widget()
		{
			sx = sy = ex = ey = 1;
		}
		static void set_new_handler(new_handler t);//用来指定当前的 new_handler,然后保存在静态成员中

		static void * operator new(size_t size); //重载new

		static new_handler s_originalnewhandler; //静态成员,保存 new_handler

		static void myhandler(void);

	};

	class NewHandlerHolder //这是一个资源管理类,在构造的时候设置一个新的 new_handler, 在析构的时候还原回来,这样做的好处,主要是做到异常安全。
	{                      //也就是说,无论是否因为抛出异常而终止程序,始终都会因为析构函数的调用,而将new_handler还原回来
	public:
		NewHandlerHolder(new_handler handler)
		{
			m_original = std::set_new_handler(handler); //设置新的new_handler并且保存旧的

		}
		~NewHandlerHolder()
		{
			std::set_new_handler(m_original); //析构的时候,还原旧的 new_handler
		}
	private:
		new_handler m_original;
	};
}


#endif

实现部分: 

#include "Widget.h"
#include <iostream>
using namespace std;

namespace Test
{


	new_handler Widget::s_originalnewhandler = 0;

	void Widget::set_new_handler(new_handler t)
	{
		s_originalnewhandler = t;
	}

	void Widget::myhandler() //其实这个handler就是一个toy
	{
		cout<<"Can't get the memory"<<endl;
	}
	void* Widget::operator new(size_t size) //
	{
		NewHandlerHolder handler(s_originalnewhandler); //换handler
		return ::operator new(size);//实际分配空间
	}

}//namespace test;

如果对于widget类,有一个子类定义如下;

class Direved:public Widget
	{
	private: 
		int cc;
		int dd;
	public: 

	};
如果我们使用 
Direved = new Direved()

这个时候子类使用的是从父类继承过来的new operator,所以如果我们要在父类的new中做一些 特别的事情,例如多分配一些内存,然后存储一些特别的数值之类的。

所以做如下检测是必要的: 

void* Widget::operator new(size_t size)
{
		
		NewHandlerHolder handler(s_originalnewhandler);
		if(size == sizeof(Widget))
		{
			//特殊的处理逻辑
		}
		return ::operator new(size);
}



3. 具有内存泄露检测的new

最后介绍一个比较有趣的,也是十分实用的一种重载new的用途: 实现具有检测内存泄露的new。

主要思想就是,在每次调用new的时候,我们将 分配出来的指针值,文件(用 __FILE__获取),调用行号( 用  __LINE__获取), 保存在一个表中

每次调用delete的时候,首先用这个指针查表,如果在表中,就将表项删除,如果不在表中认为是删除未知指针。

在程序退出之前,检查这个表,如果表中依然有没有删除的项,我们认为存在new的数据,没有delete,即内存泄露。

代码如下: 

#ifndef MEM_CHECK_NEW_H
#define MEM_CHECK_NEW_H

#ifdef _DEBUG


void *operator new(size_t size, const char *file_name, long line); //重载new, 第二个,第三个参数是文件名,和行号
void *operator new[](size_t size, const char *file_name, long line);//重载new[]

void operator delete(void *);//重载delete
void operator delete[](void *);


//#define new new(__FILE__,__LINE__)

#endif
#endif


实现部分

      

#include "mem_check_new.h"
#include <malloc.h>
#include <stdio.h>

//#include <iostream>
//using namespace std;

#ifdef _DEBUG

namespace 
{
	

	struct info //定义表中存储的数据,分配的指针值,文件名,行号
	{
		void *ptr;
		const char *file_name;
		long line;
	};


	info ptr_list[1024]; //内存分配表
	unsigned int ptrn = 0;//内存分配表项

	int find_ptr(void *p) //依据指针,查询记录这个分配的表项
	{
		for(unsigned int i = 0; i < ptrn; i++)
		{
			if(ptr_list[i].ptr == p)
			{
				return i;
			}
		}
		return -1;
	}
	void del_ptr(unsigned int i) //依据表项id,删除这个分配记录
	{
		while(i+1 < ptrn)
		{
			ptr_list[i] = ptr_list[i+1];
			i++;
		}
		ptrn--;
	}

	class process_end //在程序退出前,检测是否有泄露
	{
	public:
		~process_end()
		{
			for(unsigned int i = 0; i < ptrn; i++)
			{
				printf("file: %s, line: %d Memory leak\n", ptr_list[i].file_name, ptr_list[i].line);
			
			}
		}
	};
	process_end pe; //用一个全局对象,在程序退出前,析构这个对象,然后检测开始

}//end of namespace non;

//void* operator new(size_t size, const char *file_name,long line)
void* operator new(size_t size, const char *file_name,long line) //重载new,每次分配内存,都放入表中记录下来
{
	void *p = malloc(size);
	ptr_list[ptrn].ptr = p;
	ptr_list[ptrn].file_name = file_name;
	ptr_list[ptrn].line = line;
	ptrn++;
	return p;
}


void *operator new[](size_t size, const char *file_name, long line) //重载 new[]
{
	return operator new(size, file_name, line);
}

void operator delete(void *p) //调用delete的时候,首先在表中查找当前指针的值,如果存在就删除
{
	int i = find_ptr(p);
	if(i >= 0)
	{
		free(p);
		del_ptr(i);
	}
	else
	{
		printf("delete unknown pointer\n");
		
	}
}

void operator delete[](void *p)
{
	operator delete(p);
}


#endif

你可能感兴趣的:(C++重载new,以及实现检测内存泄露版本的new)