关闭

漫谈C++内存分配失败

发表于:2011-10-26 09:32

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:steedhorse    来源:51Testing软件测试网采编

  没错,是“漫谈”,而且“漫”得有点乱。然而,抛砖尚可引玉,想到的事情,纵然脉络不是很畅,写下来也不是坏事。开卷有益,动笔也有益。

  一切缘自一位C语言开发经验非常丰富的的朋友问我的一个问题。朋友问:“C++中的new在分配内存失败时会抛出异常(std::bad_alloc)而不返回0(一些老的编译器可能还在返回0,但这样的编译器实在“太老了”),这跟C程序员的做法很不一样。而且,许多C++程序在使用new创建对象时也根本不检查这种异常。这是一种什么哲学呢?”他还提到:“一般C程序员总会判断一下malloc失败的情况,就连Linux内核中都是如此。”

  对于他的疑惑,我首先想到的是:一般用C++实现的应用层程序,内存管理方面自不能与内核程序相提并论。OS内核直接管理物理内存,所有应用程序的地址空间均由它映射而来,然后靠它建立机制进行翻译。内核如果在内存管理方面不保险,应用层还怎么过日子?内核中的内存分配还须考虑许多其它问题,比如不同区域的不同特性(像某些DMA使用的buffer要物理连续且位于特定位置)。同样重要的:对一个成熟的OS内核来说,即使在应用程序出现严重问题的时候也不能泄露物理内存及其它资源,且不能影响其它程序。

  应用层程序则不同,它们一般拥有彼此独立的、flat的虚拟内存空间,数量上通常远大于物理内存。因此,一个应用程序如果能耗尽虚拟内存,那要么是对数据的规模估计不足,要么就是一个必需专门解决的严重bug。

  耗尽虚拟内存跟其它许多严重的bug(再比如缓冲区溢出导致的堆栈破坏)一样,多数情况下即使能检测到也常常无计可施,如果“有计可施”,那何不早施此计?何苦等它发生再亡羊补牢呢?反过来想,该失败的时候痛痛快快的快速失败,这不算坏事。至少,比带着问题继续运行半小时,然后在某个完全不相干的地方发生莫名其妙又难以重现的bug要好得多。

  这是我当时给朋友的回答,朋友勉强同意了,至少不再纠结C++程序员因何不在new的时候检查std::bad_alloc了。然而,顺着这个问题,我觉得可以联想到好多相关的话题。

  (1)首先想到的是Java语言的做法。Java中的变量都是引用(基本类型的除外),而被引用的对象是用new在堆(heap)上创建的。在Java中new一个对象时,理论上也有可能引发java.lang.OutOfMemoryError。当然,这是个Error,不是从java.lang.Exception派生的“异常”,因此语言并没强制我们catch它。然而,语言是否要求并不重要,语言为什么不要求才是重要的。显然,如果问题足够严重,即使语言不要求,Java程序员也会在每一处new的周围包上try/catch。可Java程序员没有这么做。为什么?我想关键的原因跟上面是一样的:一个应用程序耗尽虚拟内存,要么是对数据的规模估计不足(是否应通过java命令的-Xm系列参数设置更大的heap呢?),要么就是一个必须专门解决的bug。

  同时,相对C++来说,Java程序中采用这一决策还有更充分的理由:因为有GC机制,Java程序中因为粗心造成的内存泄露较少(可能会有因不良设计造成的内存伪泄露)。

  (2)C++中的“new”还不只是分配内存那么简单。对于用户自定义的类型来说,“new T;”相当于operator new再加上对T的构造函数的调用。由于类的构造函数完全可能引发异常,于是,就算内存分配一切顺利,一条new语句还是可能产生异常。看来,需要catch的不止std::bad_alloc。

  (3)暂不考虑“哲学”因素,如果有人仍然觉得应该像C程序那样严格检查内存分配,可不可以呢?当然可以,毕竟它还能抛出异常么,它能抛出我们就能捕捉。于是人们自然会想:C++或Java程序员用驼鸟策略对付内存分配的失败,异常在使用上比较麻烦的事实会不会是原因之一呢?表面看是显然的:每分配一次内存都要包上一层try/catch,跟C中的针对返回值的if/else风格比起来凌乱多了。

  实际上,那不是使用异常的正确方法。如果异常只是if/else的简单语法替代物,那它根本就没有存在的必要。异常的好处之一(真的只是“之一”)是:一个异常只需一个地方处理就足够了。比如下面这样:

  • void f1() {  
  •     try {  
  •         // ...  
  •         f2();  
  •     } catch (const some_exception& e) {  
  •         // ...  
  •     }  
  • }  
  •   
  • void f2() {  
  •     // ...  
  •     f3();  
  • }  
  •   
  • void f3() {  
  •     // ...  
  •     f4();  
  • }  
  •   
  • void f4() {  
  •     // ...  
  •     throw some_exception();  
  • }
  • 21/212>
    《2023软件测试行业现状调查报告》独家发布~

    关注51Testing

    联系我们

    快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

    法律顾问:上海兰迪律师事务所 项棋律师
    版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
    投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

    沪ICP备05003035号

    沪公网安备 31010102002173号