众所周知,C++中使用new关键字申请内存成功时会返回申请的内存起始地址,并在该地址上调用构造函数。那么,有考虑过失败的情况吗?
动态申请的处理方式
- C语言
int *p = (int*)malloc(10000000000000000000);
if(NULL == p)
{
//申请失败处理分支
}
else
{
//申请成功处理分支
//.....
free(p);
}
- C++
int* p = new[10000000000000000000];
if(NULL == p)
{
//申请失败处理分支
}
else
{
//申请成功分支
//.....
delete [] p;
}
动态申请的结果
C语言
- 成功:返回申请成功的内存起始地址
- 失败:返回NULL
C++
- 成功:返回申请成功的内存起始地址
- 失败:返回NULL或抛出std::bad_alloc异常,结果视编译器的不同而不同。
- 这是因为在早期的C++编译器中为了兼容C编译器的方式而做出的行为,而在近代,C++由于支持抛出异常来处理一些可预见的异常情况后,C++编译器就将抛出std::bad-alloc异常作为new失败的行为,so,当申请失败抛出异常时程序就不会执行到申请失败的处理分支中。
new中的异常是怎么抛出 异常的?
- new关键字在空间不足导致内存分配失败时
- 会调用全局的new_handler类型的函数(包含在头文件中)来处理调用失败的情形,这样就可能在new_handler函数中有机会腾出足够的空间使得可以申请足够的空间,但C++编译器不知道具体怎么操作,所以就有了个默认处理,即在new_handler函数里抛出异常std::bad_alloc异常。
- new_handler的类型是:void (*new_handler)(),它是一个函数指针。
- 由于编译器不知道如何整理内存 碎片,所以就可以自定义new_handler函数在不同环境中做不同的内存整理工作。
自定义new_handler函数及使用
- new_handler函数定义,函数类型需要和new_handler类型一样(即void(*)()类型),具体实现一般在工程中是整理内存碎片,也可以在此处抛出std::bad_alloc异常,由于是学习,所以使用粗暴的方式进行处理:
void my_new_handler()
{
cout << "No enough memory" << endl;
exit(1);
}
- new_handler函数使用,使用new_handler set_new_handler(new_handler)函数设置在new失败时需要调用的new_handler函数
- set_new_handler的输入参数是operator new分配内存失败时要调用的出错处理函数的指针(此处的my_new_handler),返回值是set_new_handler没调用之前就已经在起作用的旧的出错处理函数的指针(编译器默认的全局new_handler函数)。
void ex_func()
{
new_handler func = set_new_handler(my_new_handler);
if(func)//为何判断,因为有些编译器
{
func();
}
}
由于编译器不同,如何进行行为统一?
- 全局范围(风险太大,不推荐)
- 重新定义new/delete的实现(操作符重载),不抛出任何异常
- 自定义不抛出异常的new_handler函数,并设置给编译器处理申请失败的情形
- 类层次范围
- 重载new/delete函数,不抛出异常
- 单次动态内存分配
- 在new时使用nothrow参数,指明不抛出异常,此时返回空指针
new使用实例
首先进行new/delete关键字的重载:
class Test
{
int m_value;
public:
Test()
{
cout << "Test()" << endl;
m_value = 0;
}
~Test()
{
cout << "~Test()" << endl;
}
void* operator new (size_t size) //throw()
{
cout << "operator new: " << size << endl;
return NULL;//模拟new失败的情形
}
void operator delete (void* p)
{
cout << "operator delete: " << p << endl;
free(p);
}
void* operator new[] (size_t size) throw()
{
cout << "operator new[]: " << size << endl;
return NULL;//模拟new失败的情形
}
void operator delete[] (void* p)
{
cout << "operator delete[]: " << p << endl;
free(p);
}
};
实例一:证明编译器是否存在全局的new_handler处理函数
void my_new_handler()
{
cout << "No enough memory" << endl;
exit(1);
}
void ex_func_1()//证明编译器是否带有全局的new_handler处理函数
{
new_handler func = set_new_handler(my_new_handler);
try
{
cout << "func = " << func << endl;
if( func )//为何判断,因为存在一些编译器不含全局的new_handler函数
{
func();
}
}
catch(const bad_alloc&)//证明是否会抛出bad_alloc异常
{
cout << "catch(const bad_alloc&)" << endl;
}
}
- g++执行结果
- VS2010执行结果
- bcc编译器执行结果
根据上述结果可知,不同的编译器确实存在差异,三款编译器中只有bcc编译器定义了全局的默认new_handler函数,并在new失败时抛出异常。
统一编译器的行为:不抛出异常
- 在重载的new/delete中添加throw()声明告诉编译器不抛出异常
void* operator new (size_t size) throw()//声明后就只返回空指针,不抛出异常
{
cout << "operator new: " << size << endl;
return NULL;
}
- 使用new(nothrow) Test();//使用nothrow关键字告诉编译器不抛出异常
int* p = new(nothrow) int[10];//在指定空间申请内存并构造对象
// ... ...
delete[] p;
- new关键字在指定地址上分配内存
int bb[2] = {0};
struct ST
{
int x;
int y;
};
ST* pt = new(bb) ST();//指明在bb地址上创建对象
pt->x = 1;
pt->y = 2;
cout << bb[0] << endl;//通过打印值证明是在bb上创建了对象
cout << bb[1] << endl;
pt->~ST();//在地址上指明调用构造函数后需要手动调用析构函数