C++ new失败的处理

我们都知道,使用 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++》

————————————————————————————————————————————————————————————

建议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. //   new   and   delete
  9. void   *operator   new(std::size_t)   throw(std::bad_alloc);
  10. void   operator   delete(void   *)   throw();
  11. //   array   new   and   delete
  12. void   *operator   new[](std::size_t)   throw(std::bad_alloc);
  13. void   operator   delete[](void   *)   throw();
  14. //   placement   new   and   delete
  15. void   *operator   new(std::size_t,   void   *)   throw();
  16. void   operator   delete(void   *,   void   *)   throw();
  17. //   placement   array   new   and   delete
  18. void   *operator   new[](std::size_t,   void   *)   throw();
  19. 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. //  new   and   delete
  10. void   *operator   new(std::size_t,   std::nothrow_t   const   &)   throw();
  11. void   operator   delete(void   *,   std::nothrow_t   const   &)   throw();
  12. //   array   new   and   delete
  13. void   *operator   new[](std::size_t,   std::nothrow_t   const   &)   throw();
  14. 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代码形式便起了作用。所以,要针对不同的情形采取合理的处置方式。

原文转自:http://www.2cto.com/kf/201204/129171.html
请尊重原作者版权

时间: 2024-10-15 07:38:57

C++ new失败的处理的相关文章

SQL Server 2008的MSSQLSERVER 请求失败或服务未及时响应

我的是SQL server 2008R2, 以前可以正常的启动SQL server(SQLEXPRESS).SQL server(MSSQLSERVER),有几天没有打开了,就在昨天 开机之后就无法启动MSSQLSERVER了,提示的信息如下图: 快速解决办法如下: 第一步:打开事件查看器,查看windows日志,点击应用程序,查看windows错误日志 http://product.pconline.com.cn/itbk/software/win8/1211/3060037.html 第二步

XShell 连接虚拟机中的服务器 失败 、连接中断(Connection closed by foreign host.)

在使用XShell连接虚拟机中的服务器时,报以下错误并断开连接,之前连接还是挺稳定的,忽然就这样了 Last login: Thu Aug 10 21:28:38 2017 from 192.168.1.102 [[email protected] ~]# Socket error Event: 32 Error: 10053. Connection closing...Socket close. Connection closed by foreign host. Disconnected f

在MacBook Air 上装Win10的,反反复复的失败过程。

这个月初,一个女性朋友托我帮她装电脑,往MacBook Air上面装Windows 系统,原因是windows用的习惯,用起来顺手.然后用脚趾头考虑了一下,就一口答应下来了.难道这就是一个标准程序员的命么,修电脑,装系统,最近又多了一个任务,帮忙买手机.o(╯□╰)o 在家里,我先简单百度了一下Mac是什么鬼,然后顺便看了一下网上有一大堆苹果电脑装Windows 的教程,可见还是有很多的成功例子.也没就没有仔细去看,然后就放心大胆的在家里,装备个U盘,用大白菜做成U盘启动.顺便也幻想了一下,顺利

php7微信支付回调失败

升级完PHP7 发现微信支付回调失败.原来是 $GLOBALS['HTTP_RAW_POST_DATA'];没有定义的问题.php7 移除了这个全局变量. 修改如下: //获取通知的数据 $xml = $GLOBALS['HTTP_RAW_POST_DATA'];//这里在php7下不能获取数据,使用 php://input 代替 if(!$xml){ $xml = file_get_contents("php://input"); }

cnmp安装失败,报错npm ERR! enoent ENOENT: no such file or directory,

1.cnmp安装失败 2.提示如下: bogon:node_modules liangjingming$ sudo npm install cnpm -g --registry=https://registry.npm.taobao.org Password: /usr/local/lib └── (empty) npm ERR! Darwin 16.4.0 npm ERR! argv "/usr/local/Cellar/node/7.8.0/bin/node" "/usr

一次失败的却又成功的尝试机器学习经历

一遍看书一遍运行着书中提供的代码,我学习到K-NN分类算法.于是晚上找到一串数据进行尝试. 数据来自于网络1401班C语言教学平台里的数据 首先我对数据进行了简单的处理得到以下结果.这里的分类是对学生的期末考试分数(总评分)和省二级分数综合评定的 最后真正被使用的是txt格式的文件 然后使用了python读取TXT文件,用matplotlib库对数据画图 讲道理确实看不出来有什么规律,大概就是做题少的同学最后至少有一次考试不及格. 然后对数据的随堂测试.自由练习次数进行归一化处理,使用kNN模型

python+selenium+unitest用例失败重运行

经过多次研读和调试unittest代码,后来发现一个也可以重运行setUp()和dearDown()的解决办法,那就是修改源码,我们重新建一个模块套件类来覆盖原来的TestSuite类 实例代码: [python] view plain copy class Suit(unittest.TestSuite): def run(self, result, debug=False): failcount = 1#失败总运行次数 class_num = 1 topLevel = False if ge

git clone失败

git clone失败,提示输入的密码错误,执行以下两步 1. 重新生成ssh 在命令行执行命令:ssh-keygen -t rsa -C "[email protected]" 邮箱要输入自己的地址 2. 把生成的 ~/.ssh/id_rsa.pub 公共密钥添加到git网站上 记住要全部复制!

[系统] 安装Ubuntu 双系统 - 失败

因为工作原因, 所以需要装ubuntu系统. 在网络上查了一下, 一般都是使用U盘安装. 但是由于手头上既没有U盘又没有光盘,只能用硬盘安装了. 查一下, 使用wubi安装方式从硬盘安装, 非常方便. 可是安装Ubuntu14.04后, 重启系统,在进入系统的时候,失败了. 只好又开始了搜索.果然双系统没有这么好装. 因为wuli的维护跟不上系统的更新的速度, 在Ubuntu 11.04版本开始, 就问题越来越多了. 在13.04版本中直接取消了. 参考: http://forum.ubuntu

hive创建表失败,drop表失败

一.hive创建表失败,报错: CREATE TABLE pokes (foo INT, bar STRING);FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask. MetaException(message:javax.jdo.JDODataStoreException: An exception was thrown while adding/validating class(