学校里学不到的C语言教程之3学习异常处理

我们本系列的上篇文章说了自己分配内存的重要性,因为那很容易引进程序的崩溃。在原始的C/C++中,这是经常发生的一件事情。除此之外引起崩溃的原因非常多,常见的有:访问指针异常、除0错、访问NULL指针、文件访问错误以及操作系统异常等等。对于一个刚出校门走上工作岗位的C语言程序员来说,在默认的情况下C语言程序只要遇到以上情况就一定会崩溃这种事实是非常打击人的。如果你用的是vc、bcb这样著名的ide还是好的,它们会默认处理一些常见错误。如果这位初学者的岗位是在linux下的,那基本上就是灾难了:他一定会经常碰到linux下最著名的问题--段错误。段错误的表现为,写得好好的程序总会在某个时刻弹出这个信息然后就退出了。然后你就找啊找,到底哪里出了问题。你会在网上搜索各个解决办法,估计大部分找传过来后都会在各个可能出错的地方打上printf。如果你的程序是单线程的,那倒也罢了,printf会很有效的提示出错误的位置。如果你的程序是多线程的,而且还是服务器...我个人觉得,除非您所在的公司很有钱,要不这一定是个坑,几年内是出不来的,工作之余多存点钱以便被炒的时候...真的,一个刚工作的初学者能维护好一个C/C++服务器程序的可能性无限接近于0!但在我接触过使用delphi的公司中,居然有很多服务器是初学者写的,虽然其中有不少错误,但是他们写的程序是不会动不动就崩溃的。同样的道理也适用于java和C#。有一定工作经验的同学一定会同意我的这个观点。

那么这种情况是怎样造成的呢?原因就在于默认的情况下,C语言的错误处理就是直接退出!说实在的我觉得这真是一个脑残的设计思想,因为年代久远已经无法追溯这是怎样形成的了。但现状就是如此,我们只能自己想办法去处理和适应。我们先来看看其他语言是怎么处理的,就先以受众最多的java为例吧。学过java的同学应该都学过它的历史都知道java本来就是为了解决C语言的很多问题而产生的,既然错误的处理方式很烂,那java当然是要重点处理的了。java的处理方式就是加上异常处理,当出错时说清楚怎样做就可以了,不需要退出(具体代码我们就贴了),而且还要求在所有可能出错的地方都要写上异常处理,就是那个著名的try代码块。不过我个人觉得java的处理方式太深究化,实际工作中太麻烦,我个人觉得最好的方式是delphi的,它象java一样可能以显示存在,不存在的情况下则会默认只跳出一层,而不是整个程序(估计初学者也看不懂什么是跳出一层,这个一时也解释不清楚)。因为try的代码块处理方式非常有效,所以现在的语言大多这样处理。当然golang是例外的,因为try的概念已经根深蒂固,所以我花了近两周才学会golang的异常处理,总的来说golang的处理介于java和delphi之间,印象中它是唯一不使用try代码块的异常处理语言。

好了,既然业界都有处理方法了,C/C++里不可能没有这个try吧。先说好消息,是的,有的。等等,先别高兴得太早C++中的异常处理基本上只针对类的异常,对于系统的异常是一样崩溃的。而C语言里呢,各个厂商也扩展了try语言块,它的调用方式大概是这样:

  try{各种操作  }  catch(...)  {    错误处理  }这里又能正常运行了

你一定以为万事大吉了。如果您用的是bcb,那差不多吧。如果是vc情况很复杂,我们先说gcc的情况吧:gcc下完全没有用,而且默认下根本不支持try。得了,我们都用vc...好,我们来看下vc的情况,测试代码如下图:

异常测试代码

源码如下(因为可能会被过滤某些符号,所以请参考上面的图片):

int_tmain(intargc,_TCHAR*argv[]){  //char*s=;  char*s=NULL;  //chars[]=;//换成这个数组声明的方式内存就不会报错了  //char*s=(char*)malloc(11);//C++里还要加强制转换  try{  memset(s,0,11);//一定要清空  strcpy(s,abc);  //printf(HelloWorld!\r\n);  printf(HelloWorld!%s\r\n,s);  //free(s);  }  catch(...){}  printf(正常退出\r\n);//C中的try是无用的,到不了这里  return0;}

如果是bcb那么这段代码会输出正常退出的。如果是java或者delphiC#等现代语言也会,但VC的情况很复杂,以下是其版本的发布模式的编译运行结果:

VC下没有起作用

可以看到,这里的try根本没起使用。不过VC下另有__try语言块支持,并且还有debug模式,在各种选项的组合下VC是可以支持的,不过时间关系我们就不展示了。总之VC是可以做到的,不过不是用默认的C/C++异常处理方式。

bcb正常

正因为以上的C/C++异常处理的复杂性,特别是gcc下没有好的处理方法(暂时)。所以现在业界的做法大多其实是不做异常处理!是的,您没看错,至少是没有做太多的异常处理,开源界就更是如此了,开源界基本上不处理或者是用长跳转的方式(具体我们就不介绍了)。还有一种常见的就是加退出处理函数,在程序就要崩溃退出前显示一个对话框,例如常见的迅雷的错误退出对话框和firefox的错误对话框想必大家都见过吧。你会说这么恐怖!那么服务器程序怎么办?是这样的,通常服务器会弄成两个程序,一个是主业务,跑逻辑,另外一个专门监控这个主业务程序,如果前面的那个崩溃了,那么监控的这个会把它再启动...虽然不可思议,但事实就是如此,比如大家常用的php就是这样的。另外一些单机程序也是,比如早期的google浏览器的部分功能也是独立的exe在后台跑的。遗憾的是,很多国内的公司并不知道这样做。

要说的是,虽然所说gcc不久会有真正的异常支持,VC也可以实现java、C#那种级别的异常支持。但是异常不是万能的,有一些错误是无法用异常跳过的,正确的方式还是写好程序,考虑好可能发生的错误情况。比如我从业生涯中就见过好多次整个java程序都崩溃的情况,delphi也是;golang中的cgo异常也是无法完全处理的(所以我现在基本不用cgo,也希望大家发行量不要用)。有个真实的故事:过去手机程序kjava流行时有一个同时面对公司kjava程序莫名其妙的运行结果时抱怨还不如象C语言一样直接退出呢。所以异常这个东西也是双刃剑,既然现在C对它的支持还不完善那还不如不要用它!

最后我想说,我个人觉得使用监控程序的方式目前来说是最好的解决方案,当然这可能和我主要从事服务器端的开发有关系。




转载请注明:http://www.aierlanlan.com/grrz/2284.html