operator new
1.对于new和delete,大家都有一定的了解,他是C++下用来申请内存和释放内存的操作符,而在底层,new是通过调用operator new这个全局函数来实现内存的申请和释放的,而operator new这两个全局函数是系统提供的。
2.底层operator new的实现如下:
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)//其中这个函数的作用是当申请内存不成功的时候,编译器其实会自己在重新申请,这块在下面我们会讲。
{
// report no memory
// 如果再次申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
可以看到,对于operator new的操作,它的底层还是通过malloc来实现的,可是与malloc不同的是,当他申请失败的时候,他会继续在调用一个_callnewh这个函数去继续申请内存,然后如果继续申请内存不成功,那么就bad_allloc抛出异常,这个异常会被捕获后程序就退出了。
new-handler和set_new_handler
1.首先,我们先看以下代码:
int main()
{
int* ptr = new int[536870911];
return 0;
}
当我们运行以上代码的时候,程序肯定是会报错的,而如果用malloc去申请空间,就不会报错,因为上述的代码申请的字节数太大,内存不能给予足够的内存,所以会做一些事情,在new中,程序会抛出一个异常bad_alloc这个异常(这个我们会在下面去显示),而在malloc中,会直接给想要分配这些内存的指针赋值为0,然后就直接退出了。
这是new中的操作:
int main()
{
try
{
int* ptr = new int[536870911];
}
catch (bad_alloc &e)
{
cout << "bad_alloc error." << endl;
}
return 0;
}
这个程序运行完后,会打印一个bad_allic error.这一串数据,这是为什么呢,这就与我们要讲的set_new_handler有关了,但是在set_new_handler之前,我们必须了解new_handler机制。
2.new_handler机制:
①:new_handler是什么:
对于new_handler,它其实是一个被定义的指针函数,它的操作与set_new_handler是密不可分的。
②:new_handler的底层定义:
typedef void (* new_handler) ();//这为new_handler的底层定义。
所以由此可以看出来,new_handler其实就是一个被typedef出来的一个函数指针。
3.set_new_handler
①:对于set_new_handler的介绍,我们先看以下代码:
void OutOfMemory()
{
cout << "bad_alloc error." << endl;
}
int main()
{
set_new_handler(OutOfMemory);
int* ptr = new int[536870911];
return 0;
}
如果运行以上代码,就会出现这样的情况:
他会对bad_alloc error.进行无休止的打印,所以,对于set_new_handler我们了解到,它是一个当用new去分配内存,如果失败了去调用的一个函数,并且这个函数会被一直调用。
②:set_new_handler的底层定义:
new_handler set_new_handler(new_handler p)throw();
通过底层的定义,我们可以看出来new_handler和set_new_handler为什么息息相关了,对于set_new_handler,它是一个函数,它的参数是一个new_handler函数指针类型,它的返回值也是一个new_handler函数指针类型,并且它后面有throw(),证明这个函数不抛出任何异常。
所以,这个函数的作用是接收一个函数并且返回一个函数。
operator new内存开辟失败异常处理
首先:对于上面讲的还留下了一个问题,为什么在new出现错误的时候,就会一直调用new_handler函数?而set_new_handler到底做了什么?
1.operator new在内存开辟失败做了什么?
首先,在operator new内存开辟失败的时候,他不会像malloc一样给指针一个null然后当没事了,继续走程序,而对于operator new,它在申请内存空间失败后,它会想着去释放一些不必要的其他内存空间,所以在此期间会不断地去调用new_handler函数,然后释放了这些空间之后,再试着去申请内存空间,如果申请成功了,就将这些内存空间返回,如果没有申请成功,那么最后抛出一个bad_alloc异常。
2.所以,对于一个好的new_handler函数,会具有以下优点:
- 让更多的内存可被使用:就是在一开始申请空间的时候,就是申请一大块空间,当第一次new_handler函数被调用的时候,将这些多余的空间释放回去给程序去使用。这样可以让operator new 的下一次申请空间内存的成功概率会提高。
- 安装另一个new_handler:程序申请空间内存失败后,去调用new_handler函数,但是程序知道本来的new_handler函数是无法完成任务的,但是新的new_handler可以完成任务,它就可去安装新的new_handler函数去替换原来的new_hanlder函数,这个操作可以通过set_new_hander去完成。(这样当operator new的下一次申请内存空间失败后,就会调用的是新的new_handler函数)
- 卸载new_handler:将null分配给set_new_handler的参数,一旦没有安装任何new_hander,operator new 会在内存分配不成功的时候会直接抛出异常。
- 抛出bad_alloc异常:这样的异常不糊被operator new捕捉,而是被系统里面的内存检索捕捉。
- 不返回:调用abort()或者exit()。
3.对于new_handler函数的操作,需要set_new_handler的不断帮助,set_new_handler会帮助其安装新的new_handler,而set_new_handler的底层实现是这样的:
new_handler set_new_handler(new_handler p)threow()
{
new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
一级空间配置器的模拟实现
class new_alloc
{
public:
void* operator new(size_t n)
{
void* ptr = malloc(n);
if (ptr == NULL)
{
ptr = alloc(n);
}
return ptr;
}
static new_handler set_new_handler(new_handler p) //去更改new_handler函数的。
{
new_handler oldhandler = currentHandler;
currentHandler = p;
return oldhandler;
}
private:
static void* alloc(size_t n);//当申请内存失败了,就要用这个函数。
static new_handler currentHandler;
};
new_handler new_alloc::currentHandler = 0;//在类外声明
void* new_alloc::alloc(size_t n)
{
new_handler p;//首先定义一个函数指针
void* ptr;
for (;;)
{
p = currentHandler;//让他指向我们定义的这个可以解决问题的函数
if (p == 0)//如果最后问题没有解决,那么就只能退出了
{
throw bad_alloc();
}
(*p)();//使用这个函数去释放那些不需要的内存。
ptr = malloc(n);//然后重新申请
if (ptr)//如果申请成功,那么就返回指针。
return ptr;
}
}
用法:由于我们在使用这个一级空间配置器的时候,我们如果设置了出错函数了,那么我们会在申请空间的上面写出来set_new_handler,其中参数为我们设置的内存处理函数,如果没有设置,那么就默认为0去处理,默认为0处理的结果就是,如果申请内存失败,那么就会报错,并终止程序的运行。