a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 109|回复: 1

[软件设计师] 2012年软件水平考试软件设计师课后辅导(5)

[复制链接]
发表于 2012-8-2 09:08:23 | 显示全部楼层 |阅读模式
没错,是“漫谈”,而且“漫”得有点乱。然而,抛砖尚可引玉,想到的事情,纵然脉络不那么顺,写下来也不是坏事。开卷有益,动笔也有益。   一切缘自一位C语言开发经验丰富的的朋友问我的一个问题。朋友问:“C++中的new在分配内存失败时会抛出异常(std::bad_alloc)而不返回0(一些老的编译器可能还在返回0,但这样的编译器实在”太老了“),这跟C程序员的做法很不一样。而且,许多C++程序在使用new创建对象时也根本不检查这种异常。这是一种什么哲学呢?”他还提到:“一般C程序员总会判断一下malloc失败的情况,就连Linux内核中都是如此。” ; _* j* C6 \" P! O# L- O- P1 K
  当时,我首先想到的是:一般用C++实现的应用层程序,内存管理方面自不能与内核程序相提并论。OS内核需直接管理物理内存,所有应用程序的地址空间都由它映射而来,然后依靠它建立的机制进行翻译。内核如果在内存管理方面不保险,应用层还怎么过日子?另外,内核中的内存分配尚须考虑许多其它问题,比如不同区域的不同特性(像某些DMA使用的buffer要物理连续且位于特定位置)。应用层程序则不一样,它们一般拥有flat的虚拟内存空间,且数量上通常远大于物理内存。因此,一个应用程序如果能耗尽虚拟内存,那要么是对数据的规模估计不足,要么就是一个必需专门解决的严重bug.耗尽虚拟内存跟其它许多严重的bug(再如缓冲区溢出导致的堆栈破坏)一样,即使能检测到也常常无计可施,如果“有计可施”,那何不早施此计?何苦等它发生再亡羊补牢呢?反过来想,该失败的时候痛痛快快的快速失败,这不算坏事。至少,比带着问题继续运行半小时,然后在某个完全不相干的地方发生莫名其妙又难以重现的bug要好得多。
, q: w/ ]2 t+ ]0 u7 `, o  这是我当时给朋友的回答,朋友勉强同意了,至少不再纠结C++程序员因何不在new的时候检查std::bad_alloc了。然而,顺着这个问题,我自己又联想到好多东西。 + {" A& j. a7 J  ^9 [
  (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程序中不太会有因为粗心造成的内存泄露(顶多有因不良设计造成的内存伪泄露)。
9 S* o$ h1 n- j; i8 G  (2)C++中的“new”还不只是分配内存那么简单。对于用户自定义的类型来说,“new T;”相当于operator new再加上对T的构造函数的调用。由于类的构造函数完全可能引发异常,于是,就算内存分配一切顺利,一条new语句还是可能产生异常。看来,需要catch的不止std::bad_alloc. . E- X* u- e0 J8 S; t* X
  (3)暂不考虑“哲学”因素,如果有人仍然觉得应该像C程序那样严格检查内存分配,可不可以呢?当然可以,毕竟它还能抛出异常么,它能抛出我们就能捕捉。于是人们自然会想:C++或Java程序员用驼鸟策略对付内存分配的失败,异常在使用上比较麻烦会不会是原因之一呢?表面看是显然的:每分配一次内存都要包上一层try/catch,跟C中的针对返回值的if/else风格比起来凌乱多了。 & `3 x0 U, y1 I  B6 y

, ]0 h! {$ U0 l. P/ O/ ~7 u2 p' W  实际上,那不是使用异常的正确方法。如果异常只是if/else的简单语法替代物,那它根本就没有存在的必要。异常的好处之一(真的只是“之一”)是:一个异常只需一个地方处理就足够了。比如下面这样:view plain void f1() { try { // ……
回复

使用道具 举报

 楼主| 发表于 2012-8-2 09:08:24 | 显示全部楼层

2012年软件水平考试软件设计师课后辅导(5)

</p>  f2();} catch (const some_exception& e) { // ……
3 u# Q2 d; l; S9 k$ n  } - ?+ h4 W' _4 ?6 V* Y; A; }' Y
  void f2() { // ……
- T2 J9 z% J1 G  a: B: x: o* a/ J  f3();}
/ \2 M$ w; j8 b, Z3 i$ B7 |0 n# T  void f3() { // …… 0 S! T7 P  \& g/ Y6 v5 v; m
  f4();}
# l1 ~- K+ E$ ?0 b- L+ I  void f4() { // …… 7 h: q3 n  ~0 a8 R8 |
  throw some_exception();} f4惹祸,f1收场,中间f2和f3只是一脸无辜地把异常“透过去”了(在Java中尚需声明一下)--原因很可能是它们不具备足够的上下文来处理这个异常。于是,我们不用像使用返回值那样,从发生问题的地方开始,到处理问题的地方“之下”,中间每一层都要判断一下,从而写下一层层诸如:view plain x = f();if(x < 0) ) \7 s$ f8 ^5 U& Y% Z
  return x;之类的语句。这也有利于逻辑分层。
# o4 n% k% B1 \8 k  值得一提的是,在异常回滚的过程中,栈上已经构造好的对象都会正常析构。当然,这要求程序员在设计类的时候要考虑“异常安全”的因素。
/ T3 {  K- m) D  关于异常处理的思想和异常的使用,完全可以讲一本书。更有兴趣的朋友可以去找些相关书籍看看。 * g( s9 }5 W! M1 B  m3 p+ o# O

0 s6 L- _, c, Z, n  (4)事实上,C++中并非只有抛出异常的new,也有不抛异常的new,即通常所说的“nothrow new”。可以这样使用它:view plain #include // …… 8 H* a6 [& [6 N) c% S' K
  T* p = new (std::nothrow) T(/* …… */);其中,nothrow是头文件中定义的一个类型为std::nothrow_t的常量,我们可以直接使用它。这时,如果内存分配失败,p的值将为空(0),且不会有异常抛出,跟C的malloc很像了。   nothrow new实际是标准库中实现的operator new和operator new[]的重载。我们也可以根据需要自己重载operator new/operator new[],可以有全局的,也可以针对某个类重载。但实践中用的不多。 4 `% e/ u; }  Q; s8 z
  注意,使用nothrow new创建对象时,只能保证不会因为operator new或operator new[]的失败而抛出std::bad_alloc,但难保对象的构造函数不会抛出其它异常,甚至就抛出std::bad_alloc.
+ n, c7 N) v  {3 Y  (5)说到C++的内存分配,还有必要提一下set_new_handler.它允许你设置一个可以在operator new和operator new[]分配内存失败时可以回调的函数。如果你觉得还有什么办法能释放一些内存的话,这个回调函数就是最后的救命稻草了。 * c' ]$ W0 u+ m" i- `( M  H' O7 z
  (6)话说回来,多线程程序中,尤其是所谓的worker thread中,在线程函数退出之前使用“catch(……)”捕捉一下所有异常(不止std::bad_alloc)也不是完全没用。别指望能恢复什么,只求不要因为一个线程而挂掉整个程序,同时尽量保证一下数据一致性就好。另外,也别指望catch(……)能捕获一切“问题”或“bug”,没有那么好的事情。它只能捕获C++的异常,其它的问题,比如前面提到的堆栈破坏,再比如野指针访问,哪有那么容易检测得到。
/ T. d! X2 ?" X8 m  通常一个线程crash会导致整个进程crash,有人因为这个原因而更倾向于使用多进程,尤其是在类Unix的环境中。我个人对此虽不反对也不是特别赞同,因为欠债总是要还的,这也包括“技术债务”:有bug迟早还是要解决。不过,使用多进程还有别的好处,因为进程间共享数据比同一个进程的线程之间要麻烦得多,这会“迫使”开发者做出减少共享,从而既能减少并发问题又能提高并发效率的设计。 7 ~% ?/ Y4 h/ E: U+ ]! f
  (7)最后,我的另一个好朋友兼同事认为:程序crash没有那么可怕。它可能是多数客户最难以忍受的bug,但那只是源于社会心理,不见得是真正最严重的bug.
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-5-7 23:32 , Processed in 0.190580 second(s), 23 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表