我们都知道,使用 malloc/calloc 等分配内存的函数时,一定要检查其返回值是否为“空指针”(亦即检查分配内存的操作是否成功),这是良好的编程习惯,也是编写可靠程序所必需的。但是,如 果你简单地把这一招应用到 new 上,那可就不一定正确了。我经常看到类似这样的代码:
int* p = new int[SIZE];
if ( p == 0 ) // 检查 p 是否空指针
return -1;
// 其它代码
其实,这里的 if ( p == 0 ) 完全是没啥意义的。C++ 里,如果 new 分配内存失败,默认是抛出异常 的。所以,如果分配成功,p == 0 就绝对不会成立;而如果分配失败了,也不会执行 if ( p == 0 ),因为分配失败时,new 就会抛出异常跳过后面的代码 。如果你想检查 new 是否成功,应该捕捉异常 :
try {
int* p = new int[SIZE];
// 其它代码
} catch ( const bad_alloc& e ) {
return -1;
}
据说一些老的编译器里,new 如果分配内存失败,是不抛出异常的(大概是因为那时 C++ 还没加入异常机制),而是和 malloc 一样,返回空指针。不过我从来都没遇到过 new 返回空指针的情况。
当然,标准 C++ 亦提供了一个方法来抑制 new 抛出异常 ,而返回空指针:
int* p = new (std::nothrow) int; // 这样如果 new 失败了,就不会抛出异常,而是返回空指针
if ( p == 0 ) // 如此这般,这个判断就有意义了
return -1;
// 其它代码
=============================== 详解 ===================================
首先按 c++ 标准的话, new 失败会抛出 bad_alloc 异常,但是有些编译器对 c++ 标准支持不是很好,比如 vc++6.0 中 new 失败不会抛出异常,而返回 0.
// 不支持 c++ 标准的做法如下
double *ptr=new double[1000000];
if( 0 == ptr)
……处理失败……
// 标准推荐做法一。
try
{
double *ptr=new double[1000000];
}
catch(bad_alloc &memExp)
{
// 失败以后,要么 abort 要么重分配
cerr<<memExp.what()<<endl;
}
// 标准推荐做法二
是使用 set_new_handler 函数处理 new 失败。它在头文件 <new> 里大致是象下面这样定义的:
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
可以看到, new_handler 是一个自定义的函数指针类型,它指向一个没有输入参数也没有返回值的函数。 set_new_handler 则是一个输入并返回 new_handler 类型的函数。
set_new_handler 的输入参数是 operator new 分配内存失败时要调用的出错处理函数的指针,返回值是 set_new_handler 没调用之前就已经在起作用的旧的出错处理函数的指针。
可以象下面这样使用 set_new_handler :
// function to call if operator new can't allocate enough memory
void nomorememory()
{
cerr << "unable to satisfy request for memory/n";
abort();
}
int main()
{
set_new_handler(nomorememory);
int *pbigdataarray = new int[100000000];
...
}
operator new 不能满足内存分配请求时, new-handler 函数不只调用一次,而是不断重复,直至找到足够的内存。实现重复调用的代码在条款 8 里可以看到,这里我用描述性的的语言来说明:一个设计得好的 new-handler 函数必须实现下面功能中的一种。
·产生更多的可用内存。这将使 operator new 下一次分配内存的尝试有可能获得成功。实施这一策略的一个方法是:在程序启动时分配一个大的内存块,然后在第一次调用 new-handler 时释放。释放时伴随着一些对用户的警告信息,如内存数量太少,下次请求可能会失败,除非又有更多的可用空间。
·安装另一个不同的 new-handler 函数。如果当前的 new-handler 函数不能产生更多的可用内存,可能它会知道另一个 new-handler 函数可以提供更多的资源。这样的话,当前的 new-handler 可以安装另一个 new-handler 来取代它 ( 通过调用 set_new_handler) 。下一次 operator new 调用 new-handler 时,会使用最近安装的那个。 ( 这一策略的另一个变通办法是让 new-handler 可以改变它自己的运行行为,那么下次调用时,它将做不同的事。方法是使 new-handler 可以修改那些影响它自身行为的静态或全局数据。 )
·卸除 new-handler 。也就是传递空指针给 set_new_handler 。没有安装 new-handler , operator new 分配内存不成功时就会抛出一个标准的 std::bad_alloc 类型的异常。
·抛出 std::bad_alloc 或从 std::bad_alloc 继承的其他类型的异常。这样的异常不会被 operator new 捕捉,所以它们会被送到最初进行内存请求的地方。 ( 抛出别的不同类型的异常会违反 operator new 异常规范。规范中的缺省行为是调用 abort ,所以 new-handler 要抛出一个异常时,一定要确信它是从 std::bad_alloc 继承来的。想更多地了解异常规范 )
·没有返回。典型做法是调用 abort 或 exit 。 abort/exit 可以在标准 c 库中找到 ( 还有标准 c++ 库 ) 。
上面的选择给了你实现 new-handler 函数极大的灵活性。
更多知识请参考《 Effective c++ 》