码迷,mamicode.com
首页 > 其他好文 > 详细

new内存失败后的正确处理(转)

时间:2014-11-10 19:30:55      阅读:229      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   io   color   ar   使用   sp   

  本文摘录自建议30:new内存失败后的正确处理

  应该有很多的程序员对比尔盖茨的这句话有所耳闻:

  对于任何一个人而言,640KB应当是足够的了。(640K ought to be enough for everybody.)

  不幸的是,伟大的比尔盖茨也失言了。随着硬件水平的发展,内存变得越来越大,但是似乎仍不能满足人们对内存日益增长的需求。所以呢,我们C/C++程序员在写程序时也必须考虑一下内存申请失败时的处理方式。

  通常,我们在使用new进行内存分配的时候,会采用以下的处理方式:

1 char *pStr = new string[SIZE];  
2 if(pStr == NULL)  
3 {  
4     ... // Error processing  
5     return false;  
6 }  

  你能发现上述代码中存在的问题吗?这是一个隐蔽性极强的臭虫(Bug)。

  我们沿用了C时代的良好传统:使用 malloc 等分配内存的函数时,一定要检查其返回值是否为“空指针”,并以此作为检查分配内存操作是否成功的依据,这种Test-for-NULL代码形式是一种良好的编程习惯,也是编写可靠程序所必需的。可是,这种完美的处理形式必须有一个前提:若new失败,其返回值必须是NULL。只有这样才能保证上述看似“逻辑正确、风格良好”的代码可以正确运行。

  那么new失败后编译器到底是怎么处理的?在很久之前,即C++编译器的蛮荒时代,C++编译器保留了C编译器的处理方式:当operator new不能满足一个内存分配请求时,它返回一个NULL 指针。这曾经是对C的malloc函数的合理扩展。然而,随着技术的发展,标准的更新,编译器具有了更强大的功能,类也被设计得更漂亮,新时代的new在申请内存失败时具备了新的处理方式:抛出一个bad_alloc exception(异常)。所以,在新的标准里,上述Test-for-NULL处理方式不再被推荐和支持。

  如果再回头看看本建议开头的代码片段,其中的 if (pStr == 0 )从良好的代码风格突然一下变成了毫无意义。在C++里,如果 new 分配内存失败,默认是抛出异常。所以,如果分配成功,pStr == 0就绝对不会成立;而如果分配失败了,也不会执行if ( pStr == 0 ),因为分配失败时,new 就会抛出异常并跳过后面的代码。

  为了更加明确地理解其中的玄机,首先看看相关声明:

 1 namespace   std  
 2 {  
 3     class   bad_alloc  
 4     {  
 5             //   ...  
 6     };  
 7 }  
 8  
 9 //   new   and   delete  
10 void   *operator   new(std::size_t)   throw(std::bad_alloc);  
11 void   operator   delete(void   *)   throw();  
12  
13 //   array   new   and   delete  
14 void   *operator   new[](std::size_t)   throw(std::bad_alloc);  
15 void   operator   delete[](void   *)   throw();  
16  
17 //   placement   new   and   delete  
18 void   *operator   new(std::size_t,   void   *)   throw();  
19 void   operator   delete(void   *,   void   *)   throw();  
20  
21 //   placement   array   new   and   delete  
22 void   *operator   new[](std::size_t,   void   *)   throw();  
23 void   operator   delete[](void   *,   void   *)   throw();

  在以上的new操作族中,只有负责内存申请的operator new才会抛出异常std::bad_alloc。如果出现了这个异常,那就意味着内存耗尽,或者有其他原因导致内存分配失败。所以,按照C++标准,如果想检查 new 是否成功,则应该捕捉异常:

1 try  
2 {  
3     int* pStr = new string[SIZE];  
4 ...  // processing codes  
5 }  
6 catch ( const bad_alloc& e )  
7 {  
8     return -1;  
9 } 

  但是市面上还存在着一些古老编译器的踪迹,这些编译器并不支持这个标准。同时,在这个标准制定之前已经存在的很多代码,如果因为标准的改变而变得漏洞百出,肯定会引起很多人抗议。C++标准化委员会并不想遗弃这些 Test-for-NULL的代码,所以他们提供了operator new 的另一种可选形式— nothrow ,用以提供传统的Failure-yields-NULL行为。

  其实现原理如下所示:

 1 void * operator new(size_t cb, const std::nothrow_t&) throw()  
 2 { 
 3     char *p;  
 4 try  
 5 {  
 6     p = new char[cb];  
 7 }  
 8 catch (std::bad_alloc& e)  
 9 {  
10     p = 0;  
11 }  
12     return p;  
13 } 

  文件中也声明了nothrow new的重载版本,其声明方式如下所示:

 1 namespace   std  
 2 {  
 3      struct   nothrow_t  
 4      {  
 5         //   ...  
 6      };  
 7      extern   const   nothrow_t   nothrow;  
 8 }  
 9  
10 //  new   and   delete  
11 void   *operator   new(std::size_t,   std::nothrow_t   const   &)   throw();  
12 void   operator   delete(void   *,   std::nothrow_t   const   &)   throw();  
13  
14 //   array   new   and   delete  
15 void   *operator   new[](std::size_t,   std::nothrow_t   const   &)   throw();  
16 void   operator   delete[](void   *,   std::nothrow_t   const   &)   throw(); 

  如果采用不抛出异常的new形式,本建议开头的代码片段就应该改写为以下形式:

1 int* pStr = new(std::nothrow) string[SIZE];  
2 if(pStr==NULL)  
3 {  
4      ...  // 错误处理代码  
5 } 

  根据建议29可知,编译器在表达式 new (std::nothrow) ClassName中一共完成了两项任务。首先,operator new 的 nothrow 版本被调用来为一个ClassName object分配对象内存。假如这个分配失败,operator new返回null指针;假如内存分配成功,ClassName 的构造函数则被调用,而在此刻,对象的构造函数就能做任何它想做的事了。如果此时它也需要new 一些内存,但是没有使用 nothrow new形式,那么,虽然在"new (std::nothrow) ClassName" 中调用的operator new 不会抛出异常,但其构造函数却无意中办了件错事。假如它真的这样做了,exception就会像被普通的operator new抛出的异常一样在系统里传播。所以使用nothrow new只能保证operator new不会抛出异常,无法保证"new (std::nothrow) ClassName"这样的表达式不会抛出exception。所以,慎用nothrow new。

  最后还需要说明一个比较特殊但是确实存在的问题:在Visual C++ 6.0 中目前operator new、operator new(std::nothrow) 和 STL 之间不兼容、不匹配,而且不能完全被修复。如果在非MFC项目中使用Visual C++6.0中的STL,其即装即用的行为可能导致STL在内存不足的情况下让应用程序崩溃。对于基于MFC的项目,STL是否能够幸免于难,完全取决于你使用的 STL 针对operator new的异常处理。这一点,在James Hebben的文章《不要让内存分配失败导致您的旧版 STL 应用程序崩溃》中进行了详细的介绍,如果你在使用古老的Visual C++ 6.0编译器,而且对这个问题充满兴趣,请Google之。

  请记住:

  当使用new申请一块内存失败时,抛出异常std::bad_alloc是C++标准中规定的标准行为,所以推荐使用try{ p = new int[SIZE]; } catch( std::bad_alloc ) { ... }的处理方式。但是在一些老旧的编译器中,却不支持该标准,它会返回NULL,此时具有C传统的Test_for_NULL代码形式便起了作用。所以,要针对不同的情形采取合理的处置方式。

 

new内存失败后的正确处理(转)

标签:des   style   blog   http   io   color   ar   使用   sp   

原文地址:http://www.cnblogs.com/xiehongfeng100/p/4087818.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!