首页 > 代码库 > 外传篇3 动态内存申请的结果
外传篇3 动态内存申请的结果
1. 必须知道的事实
(1)常见的动态内存分配代码
//C代码 int* p = (int*)malloc(10 * sizeof(int)); if(p != NULL){ //... } //C++代码 int* p = new int[10]; if(p != NULL){ //... }
(2)必须知道的事实
①malloc函数申请失败时,返回NULL值。
②new关键字申请失败时,则会根据编译器的不同,有的返回NULL值,而有的抛出std::bad_alloc异常。
2. new operator和operator new的区别
(1)new operator是C++内建的,无法改变其行为。主要做两件事:第一,分配足够的内存,相当于调用operator new。第二,调用对象构造函数。
(2)重载operator new操作符
①只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,如果有new_handler,则调用new_handler;否则如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常;否则返回0。(注意这个调用顺序!)
②重载时,返回类型必须声明为void*
③重载时,第一个参数类型必须为表达要求分配空间的大小(字节,类型size_t)
④重载时,可以带其它参数。
(3)全局operator new伪代码
extern void* operator new( size_t size ) { if( size == 0 ) size = 1; // 这里保证像 new T[0] 这样得语句也是可行的 void *last_alloc; while( !(last_alloc = malloc( size )) ) { if( _new_handler ) ( *_new_handler )(); //调用handler函数,失败时会抛std::bad_alloc else return 0; //如果没有设置new_handler,则失败时返回NULL } return last_alloc; //成功时,返回对象地址。 }
3. new_handler()的定义和使用
//new_handler()的定义 void my_new_handler() { cout << "Not enough memory" << endl; exit(1); } //设置new_handler int main() { set_new_handler(my_new_handler); //... return 0; }
4. 跨编译器统一new的行为,提高代码移植性——解决方案
(1)全局范围(不推荐)
①重新定义new/delete的实现,不抛出任何异常(这样失败时,返回NULL)
②自定义new_handler()函数,不抛出任何异常(这样失败时,返回NULL)
(2)类层次范围:重载new/delete,不抛出任何异常(这样失败时,返回NULL)
(3)单次动态内存分配:使用nothrow参数,指明new不抛出异常(这样失败时,返回NULL)
【编程实验】动态内存申请
#include <iostream> #include <cstdlib> #include <new> //for new_handler #include <exception> //for bad_alloc using namespace std; class Test { int m_value; public: Test() { cout << "Test()" << endl; m_value = 0; } ~Test() { cout << "~Test()" << endl; } //重载new操作符 void* operator new(unsigned int size) //throw() { cout << "operator new: " << size << endl; //return malloc(size); return NULL; } //重载delete操作符 void operator delete(void* p) { cout << "operator delete: " << p << endl; free(p); } void* operator new[](unsigned int size) //throw() { cout << "operator new[]: " << size << endl; //return malloc(size); return NULL; } void operator delete[](void* p) { cout << "operator delete[]: " << p << endl; free(p); } }; void my_new_handler() { cout << "void my_new_handler()" << endl; } //判断是否存在默认的new_handler全局函数及其所抛出的异常类型 void func1() { new_handler func = set_new_handler(my_new_handler); try { cout << "func = " << func << endl; //如果存在默认的new_handler函数,则执行它。 //默认下g++和VC++编译器没有设置这样一个函数。但bcc会存在这样的new_handler,并且会抛出bad_alloc异常 if(func){ func(); } }catch(const bad_alloc&){ cout << "catch(const bad_alloc&)" << endl; } } //统一编译器的new行为 void func2() { //出现段错误的原因及解决方案: //(1)当调用new operator时,会找到重载的operator new,紧接着调用Test的构造函数。 //如果重载的operator new函数未加异常规格说明,表示可以抛出任何类型的异常。g++编译器认为:未加异常说明的 //operator new,调用失败时是会抛出异常的。如果没有抛出异常就说明分配内存是成功的,所以会继续调用对象 //的构造函数。但由于我们重载的new返回的是NULL,这恰恰骗了g++编译器一把,所以会出现给0地址对象的成员 //(m_value)赋值。因此,出现段错误。而bcc和VC++编译器不管抛不抛出异常,在调用构造函数前都会判断对象指 //针是否为NULL,为NULL则不调用构造函数。 //(2)为了统一编译器的行为,在operator new函数后加throw()异常规格说明,表示该函数不会抛出异常。这样 //编译器在调用对象的构造函数前就会主动判断对象指针是否为NULL,如果为NULL则不调用构造函数。 //创建对象 Test* pt = new Test(); cout << "pt = " << pt << endl; delete pt; //创建数组对象 pt = new Test[5]; cout << "pt = " << pt << endl; delete[] pt; } //使用单次动态内存分配 void func3() { //使用nothrow指明new不抛出异常 int* p = new(nothrow) int[10]; //nothrow表示不抛出异常,需要自行判断p是否为NULL //.... delete[] p; //place new的其它用法 int bb[2]={0}; struct ST { int x; int y; ~ST() { cout << "(x, y) :"; cout << "(" << x << ", " << y << ")" << endl; } }; //在bb数组栈空间new一个对象 ST* pt = new(bb) ST(); pt->x = 1; pt->y = 2; cout << bb[0] << endl; //输出:1 cout << bb[1] << endl; //输出:2 pt->~ST(); //显式调用析构函数 //delete pt; } int main() { //func1(); //func2(); func3(); }
5. 小结
(1)不是所有的编译器都遵循C++的标准规范
(2)编译器可能重定义new的实现,并在实现中抛出bad_alloc异常
(3)编译器的默认实现中,可能没有设置全局的new_handler函数。
(4)不同编译器在动态内存分配上的实现细节不同。对于移植性要求较高的代码,需要考虑new的具体细节。
外传篇3 动态内存申请的结果