语言|C语言中的异常处理

原文地址为: C语言中的异常处理

一 前言:
异常处理,对于做面向对象开发的开发者来说是再熟悉不过了,例如在C#中有
try
{
...
}
catch( Exception e){...}
finally{
.....
}
在C++中,我们常常会使用
try{}
...
catch(){}
块来进行异常处理。
【语言|C语言中的异常处理】说了那么多,那么到底什么是异常处理呢?
异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常情况的方法。
异常处理一般有两种模型,一种是"终止模型",一种是"恢复模型"
"终止模型":在这种模型中,将假设错误非常关键,将以致于程序无法返回到异常发生的地方继续执行.一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行.
"恢复模型":异常处理程序的工作是修正错误,然后重新尝试调动出问题的方法,并认为的二次能成功. 对于恢复模型,通常希望异常被处理之后能继续执行程序.在这种情况下,抛出异常更像是对方法的调用--可以在Java里用这种方法进行配置,以得到类似恢复的行为.(也就是说,不是抛出异常,而是调用方法修正错误.)或者,把try块放在while循环里,这样就可以不断的进入try块,直到得到满意的结果.

二 面向对象中的异常处理
大致了解了什么是异常处理后,由于异常处理在面向对象语言中使用的比较普遍,我们就先以C++为例,做一个关于异常处理的简单例子:
问题:求两个数相除的结果。
这里,隐藏这一个错误,那就是当除数为0时,会出现,所以,我们得使用异常处理来捕捉这个异常,并抛出异常信息。
具体看代码:

1#include< iostream >
2#include< exception >
3usingnamespacestd;
4classDivideError: publicexception
5{
6public :
7DivideError::DivideError():exception(){}
8constchar *what(){
9return" 试图去除一个值为0的数字 " ;
10}
11
12};
13doublequotion( intnumerator, intdenominator)
14{
15if ( 0 == denominator)// 当除数为0时,抛出异常
16throwDivideError();
17returnstatic_cast < double > (numerator) / denominator;
18}
19intmain()
20{
21intnumber1; // 第一个数字
22intnumber2; // 第二个数字
23doubleresult;
24cout << " 请输入两个数字: ";
25while (cin >> number1 >> number2){
26try {
27result = quotion(number1,number2);
28cout << " 结果是 : " << result << endl;
29
30}// end try
31catch (DivideError& divException){
32cout << " 产生异常: "
33<< divException.what() << endl;
34}
35}
36
37}
38

在这个例子中,我们使用了头文件中的exception类,并使DivideError类继承了它,同时重载了虚方法what(),以给出特定的异常信息。
而C#中的异常处理类则封装的更有全面,里面封装了常用的异常处理信息,这里就不多说了。


三 C语言中的异常处理
在C语言中异常处理一般有这么几种方式:
1.使用标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于头文件中。
2.使用assert(断言)宏调用,位于头文件中,当程序出错时,就会引发一个abort()。
3.使用errno全局变量,由C运行时库函数提供,位于头文件中。
4.使用goto语句,当出错时跳转。
5.使用setjmp,longjmp进行异常处理。
接下来,我们就依次对这几种方式来看看到底是怎么做的:
我们仍旧以前面处理除数为0的异常为例子。
1.使用exit()函数进行异常终止:

1#include< stdio.h >
2#include< stdlib.h >
3doublediva( doublenum1, doublenum2)// 两数相除函数
4{
5doublere;
6re = num1 / num2;
7returnre;
8}
9intmain()
10{
11doublea,b,result;
12printf( " 请输入第一个数字: " );
13scanf( " %lf " , & a);
14printf( " 请输入第二个数字: " );
15scanf( " %lf " , & b);
16if ( 0 == b)// 如果除数为0终止程序
17exit(EXIT_FAILURE);
18result = diva(a,b);
19printf( " 相除的结果是: %.2lf\n " ,result);
20return0 ;
21} 其中exit的定义如下:
_CRTIMP void __cdecl __MINGW_NOTHROW exit (int) __MINGW_ATTRIB_NORETURN;
exit的函数原型:void exit(int)由此,我们也可以知道EXIT_FAILURE宏应该是一个整数,exit()函数的传递参数是两个宏,一个是刚才看到的EXIT_FAILURE,还有一个是EXIT_SUCCESS从字面就可以看出一个是出错后强制终止程序,而一个是程序正常结束。他们的定义是:
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
到此,当出现异常的时候,程序是终止了,但是我们并没有捕获到异常信息,要捕获异常信息,我们可以使用注册终止函数atexit(),它的原型是这样的:int atexit(atexit_t func);
具体看如下程序:

1#include< stdio.h >
2#include< stdlib.h >
3voidException( void )// 注册终止函数,通过挂接到此函数,捕获异常信息
4{
5printf( " 试图去除以一个为0的数字,出现异常!\n " );
6}
7intmain()
8{
9doublea,b,result;
10printf( " 请输入第一个数字: " );
11scanf( " %lf " , & a);
12printf( " 请输入第二个数字: " );
13scanf( " %lf " , & b);
14if ( 0 == b)// 如果除数为0终止程序 ,并挂接到模拟异常捕获的注册函数
15{
16
17atexit(Exception);
18exit(EXIT_FAILURE);
19}
20result = diva(a,b);
21printf( " 相除的结果是: %.2lf\n " ,result);
22return0 ;
23} 这里需要注意的是,atexit()函数总是被执行的,就算没有exit()函数,当程序结束时也会被执行。并且,可以挂接多个注册函数,按照堆栈结构进行执行。abort()函数与exit()函数类似,当出错时,能使得程序正常退出,这里就不多说了。
2.使用assert()进行异常处理:
assert()是一个调试程序时经常使用的宏,切记,它不是一个函数,在程序运行时它计算括号内的表达式,如果表达式为FALSE(0),程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。
另外需要注意的是:assert只有在Debug版本中才有效,如果编译为Release版本则被忽略。
我们就前面的问题,使用assert断言进行异常终止操作:构造可能出现出错的断言表达式:assert(number!=0)这样,当除数为0的时候,表达式就为false,程序报告错误,并终止执行。
代码如下:

语言|C语言中的异常处理
文章图片
语言|C语言中的异常处理
文章图片
代码 #include< stdio.h >
#include< assert.h >
doublediva( doublenum1, doublenum2)// 两数相除函数
{
doublere;
re = num1 / num2;
returnre;
}
intmain()
{
printf( " 请输入第一个数字: " );
scanf( " %lf " , & a);
printf( " 请输入第二个数字: " );
scanf( " %lf " , & b);
assert( 0 != b); // 构造断言表达式,捕获预期异常错误
result = diva(a,b);
printf( " 相除的结果是: %.2lf\n " ,result);
return0 ;
} 3.使用errno全局变量,进行异常处理:
errno全局变量主要在调式中,当系统API函数发生异常的时候,将errno变量赋予一个整数值,根据查看这个值来推测出错的原因。
其中的各个整数值都有一个相应的宏定义,表示不同的异常原因:

语言|C语言中的异常处理
文章图片
语言|C语言中的异常处理
文章图片
代码 #defineEPERM1/* Operation not permitted */
#defineENOFILE2/* No such file or directory */
#defineENOENT2
#defineESRCH3/* No such process */
#defineEINTR4/* Interrupted function call */
#defineEIO5/* Input/output error */
#defineENXIO6/* No such device or address */
#defineE2BIG7/* Arg list too long */
#defineENOEXEC8/* Exec format error */
#defineEBADF9/* Bad file descriptor */
#defineECHILD10/* No child processes */
#defineEAGAIN11/* Resource temporarily unavailable */
#defineENOMEM12/* Not enough space */
#defineEACCES13/* Permission denied */
#defineEFAULT14/* Bad address */
/*15 - Unknown Error*/
#defineEBUSY16/* strerror reports "Resource device" */
#defineEEXIST17/* File exists */
#defineEXDEV18/* Improper link (cross-device link?) */
#defineENODEV19/* No such device */
#defineENOTDIR20/* Not a directory */
#defineEISDIR21/* Is a directory */
#defineEINVAL22/* Invalid argument */
#defineENFILE23/* Too many open files in system */
#defineEMFILE24/* Too many open files */
#defineENOTTY25/* Inappropriate I/O control operation */
/*26 - Unknown Error*/
#defineEFBIG27/* File too large */
#defineENOSPC28/* No space left on device */
#defineESPIPE29/* Invalid seek (seek on a pipe?) */
#defineEROFS30/* Read-only file system */
#defineEMLINK31/* Too many links */
#defineEPIPE32/* Broken pipe */
#defineEDOM33/* Domain error (math functions) */
#defineERANGE34/* Result too large (possibly too small) */
/*35 - Unknown Error*/
#defineEDEADLOCK36/* Resource deadlock avoided (non-Cyg) */
#defineEDEADLK36
/*37 - Unknown Error*/
#defineENAMETOOLONG38/* Filename too long (91 in Cyg?) */
#defineENOLCK39/* No locks available (46 in Cyg?) */
#defineENOSYS40/* Function not implemented (88 in Cyg?) */
#defineENOTEMPTY41/* Directory not empty (90 in Cyg?) */
#defineEILSEQ42/* Illegal byte sequence */ 这里我们就不以前面的除数为0的例子来进行异常处理了,因为我不知道如何定义自己特定错误的errno,如果哪位知道,希望能给出方法。我以一个网上的例子来说明它的使用方法:

语言|C语言中的异常处理
文章图片
语言|C语言中的异常处理
文章图片
代码 #include< errno.h >
#include< math.h >
#include< stdio.h >
intmain( void )
{
errno=0 ;
if(NULL==fopen( " d:\\1.txt " ," rb " ))
{
printf( " %d " , errno);
}
else
{
printf( " %d " , errno);
}
return0 ; }
这里试图打开一个d盘的文件,如果文件不存在,这是查看errno的值,结果是2、
当文件存在时,errno的值为初始值0。然后查看值为2的错误信息,在宏定义那边#defineENOFILE2/* No such file or directory */
便知道错误的原因了。
4.使用goto语句进行异常处理:
goto语句相信大家都很熟悉,是一个跳转语句,我们还是以除数为0的例子,来构造一个异常处理的例子:

语言|C语言中的异常处理
文章图片
语言|C语言中的异常处理
文章图片
代码 #include< stdio.h >
doublediva( doublenum1, doublenum2)// 两数相除函数
{
doublere;
re = num1 / num2;
returnre;
}
intmain()
{
inttag = 0 ;
doublea,b,result;
if ( 1 == tag)
{
Throw:
printf( " 除数为0,出现异常\n " );
}
tag = 1 ;
printf( " 请输入第一个数字: " );
scanf( " %lf " , & a);
printf( " 请输入第二个数字: " );
scanf( " %lf " , & b);
if (b == 0 )// 捕获异常(或许这么说并不恰当,暂且这么理解)
gotoThrow; // 抛出异常
result = diva(a,b);
printf( " %d\n " ,errno);
printf( " 相除的结果是: %.2lf\n " ,result);

return0 ;
} 5.使用setjmp和longjmp进行异常捕获与处理:
setjmp和longjmp是非局部跳转,类似goto跳转作用,但是goto语句具有局限性,只能在局部进行跳转,当需要跳转到非一个函数内的地方时就需要用到setjmp和longjmp。setjmp函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。异常处理基本方法:
使用setjmp设置一个跳转点,然后在程序其他地方调用longjmp跳转到该点(抛出异常).
代码如下所示:

#include< stdio.h >
#include< setjmp.h >
jmp_buf j;
voidException( void )
{
longjmp(j, 1 );
}
doublediva( doublenum1, doublenum2)// 两数相除函数
{
doublere;
re = num1 / num2;
returnre;
}
intmain()
{
doublea,b,result;


printf( " 请输入第一个数字: " );
scanf( " %lf " , & a);
printf( " 请输入第二个数字: " );
if (setjmp(j) == 0 )
{
scanf( " %lf " , & b);
if ( 0 == b)
Exception();
result = diva(a,b);
printf( " 相除的结果是: %.2lf\n " ,result);
}
else
printf( " 试图除以一个为0的数字\n " );
return0 ;
}

四 总结:

除了以上几种方法之外,另外还有使用信号量等等方法进行异常处理。当然在实际开发中每个人都有各种调式的技巧,而且这文章并不是说明异常处理一定要这样做,这只是对一般做法的一些总结,也不要乱使用异常处理,如果弄的不好就严重影响了程序的效率和结构,就像设计模式一样,不能胡乱使用。








转载请注明本文地址: C语言中的异常处理

    推荐阅读