C++ 内存分配(new,operator new)面面观 (七)

2014-11-23 23:33:58 · 作者: · 浏览: 31
;

A* p2 = new A;
delete p2;

system("pause");
return 0;
}输出:


注意:需要将类的声明实现与new的使用隔离开来。并且将类头文件放在宏定义之前。否则在类A中的operator new重载中的new会被宏替换,整个函数就变成了: void* operator new(__FILE__, __LINE__)(size_t size, char* file, int line)
编译器自然会报错。
2.内存池优化
operator new的另一个大用处就是内存池优化,内存池的一个常见策略就是分配一次性分配一块大的内存作为内存池(buffer或pool),然后重复利用该内存块,每次分配都从内存池中取出,释放则将内存块放回内存池。在我们客户端调用的是new运算符,我们可以改写operator new函数,让它从内存池中取出(当内存池不够时,再从系统堆中一次性分配一块大的),至于构造和析构则在取出的内存上进行,然后再重载operator delete,它将内存块放回内存池。关于内存池和operator new在参考文献中有一篇很好的文章。这里就不累述了。
3.STL中的new
在SGI STL源码中,defalloc.h和stl_construct.h中提供了最简单的空间配置器(allocator)封装,见《STL源码剖析》P48。它将对象的空间分配和构造分离开来,虽然在defalloc.h中仅仅是对::operator new和::operator delete的一层封装,但是它仍然给STL容器提供了更加灵活的接口。SGI STL真正使用的并不是defalloc.h中的分配器,而是stl_alloc.h中的SGI精心打造的"双层级配置器",它将内存池技术演绎得淋漓尽致,值得细细琢磨。顺便提一下,在stl_alloc.h中并没有使用::operator new/delete 而直接使用malloc和free。具体缘由均可参见《STL源码剖析》。


五 delete的使用
delete的使用基本和new一致,包括operator delete的重载方式这些都相似,只不过它的参数是void*,返回值为void。但是有一点需要注意,operator delete的自定义参数重载并不能手动调用。比如
[cpp]
void* operator new(size_t size, int x)
{
cout<<" x = "< return malloc(size);
}
void operator delete(void* p, int x)
{
cout<<" x = "< free(p);
}

void* operator new(size_t size, int x)
{
cout<<" x = "< return malloc(size);
}
void operator delete(void* p, int x)
{
cout<<" x = "< free(p);
}

如下调用是无法通过的:
A* p = new(3) A;//Ok
delete(3) p;//error C2541: “delete”: 不能删除不是指针的对象
那么重载operator delete有什么作用?如何调用?事实上以上自定义参数operator delete 只在一种情况下被调用:当new运算符抛出异常时。
可以这样理解,只有在new运算符中,编译器才知道你调用的operator new形式,然后它会调用对应的operator delete。一旦出了new运算符,编译器对于你自定义的new将一无所知,因此它只会按照你指定的delete运算符形式来调用operator delete,而至于为什么不能指定调用自定义delete(也就是只能老老实实delete p),这个就不知道了。
细心观察的话,上面operator new用于调试的例子代码中,由于我们没有给出operator new对应的operator delete。在VS2008下会有如下警告:
warning C4291: “void *A::operator new(size_t,const char *,int)”: 未找到匹配的删除运算符;如果初始化引发异常,则不会释放内存


六 关于new和内存分配的其他
1.set_new_handler
还有一些零散的东西没有介绍到,比如set_new_handler可以在malloc(需要调用set_new_mode(1))或operator new内存分配失败时指定一个入口函数new_handler,这个函数完成自定义处理(继续尝试分配,抛出异常,或终止程序),如果new_handler返回,那么系统将继续尝试分配内存,如果失败,将继续重复调用它,直到内存分配完毕或new_handler不再返回(抛出异常,终止)。下面这段程序完成这个测试:
[cpp]
#include
#include // 使用_set_new_mode和set_new_handler
void nomem_handler()
{
std::cout<<"call nomem_handler"< }
int main()
{
_set_new_mode(1); //使new_handler有效
set_new_handler(nomem_handler);//指定入口函数 函数原型void f();
std::cout<<"try to alloc 2GB memory...."< char* a = (char*)malloc(2*1024*1024*1024);
if(a)
std::cout<<"ok...I got it"< free(a);
system("pause");
}

#include
#include // 使用_set_new_mode和set_new_handler
void nomem_handler()
{
std::cout<<"call nomem_handler"< }
int main()
{
_set_new_mode(1); //使new_handler有效
set_new_handler(nomem_handler);//指定入口函数 函数原型void f();
std::cout<<"try to alloc 2GB memory...."< char* a = (char*)malloc(2*1024*1024*1024);
if(a)
std::cout<<"ok...I got it"< free(a);
system("pause");
}

程序运行后会一直输出call nomem_handler 因为函数里面只是简单输出,返回,系统尝试分配失败后,调用nomem_handler函数,由于该函数并没有起到实际作用(让可分配内存增大),因此返回后系统再次尝试分配失败,再调用nomem_handler,循环下去。
在SGI STL中的也有个仿new_handler函数:oom_malloc


2.new分配数组
A* p = new A[3];中,会直接调用全局的operator new[](size_t size),而不管A中是否有operator new[]的重载。而delete[]p却会优先调用A::operator delete[](void*)(如果A中有重载)。另外还要注意的是,在operator new[](size_t size)中传入的并不是sizeof(A)*3。而要在对象数组的大小上加上一个额外数据,用于编译器区分对象数组指针和对象指针以及对象数组大小。在VS2008下这个额外数据占4个字节,