在c++中,new作为一个操作符,也是可以被重载的,这个可能很多人比较陌生。在 Effective C++这本书中,专门提到了这方面的知识,看过此书,做一些总结,顺便在网上找到一些内容,实现一个可以检测内存泄露的内存分配机制(new delete)。
在铺叙重载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.
首先说一下重载new的必要性,
1. 用来检测运用上的错误,例如内存泄露,多次删除一个指针等,最后我们将给出一个实现检测内存泄露的new
2. 强化效能。因为自带的new函数是一个行为比较中庸的函数,对于大内存,小内存,多线程都考虑。所以自然效率会低一些,所以我们可以根据自己的需求制定new,例如在完全单线程的情况下不用考虑线程安全的问题。
3. 为了收集使用上的统计数据。在实现一个真正使用自己程序的new和delete之前,应该搜集一些程序使用动态内存的特点,例如大小,寿命等。这个时候可以重载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(pMem)) = signiture; //在之前存储签名
*(reinterpret_cast(static_cast(pMem) + realSize - sizeof(int))) = signiture; //在之后存储签名,要做指针类型转换,实现 +-
return static_cast(pMem) + sizeof(int); //返回实际可用内存
}
当然可以专门为class实现一个new操作,而不是用全局的new来实例化一个对象。
下面的实现就是一个例子:
#ifndef WIDGET_H
#define WIDGET_H
#include
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
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"<
如果对于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);
}
最后介绍一个比较有趣的,也是十分实用的一种重载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
#include
//#include
//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