在C语言中异常处理一般有这么几种方式:
1.使用标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于<stdlib.h>头文件中。
2.使用assert(断言)宏调用,位于头文件<assert.h>中,当程序出错时,就会引发一个abort()。
3.使用errno全局变量,由C运行时库函数提供,位于头文件<errno.h>中。
4.使用goto语句,当出错时跳转。
5.使用setjmp,longjmp进行异常处理。
接下来,我们就依次对这几种方式来看看到底是怎么做的:
我们仍旧以前面处理除数为0的异常为例子。
1.使用exit()函数进行异常终止:
1 #include <stdio.h> 2 #include <stdlib.h> 3 double diva(double num1,double num2) //两数相除函数 4 { 5 double re; 6 re=num1/num2; 7 return re; 8 } 9 int main() 10 { 11 double a,b,result; 12 printf("请输入第一个数字:"); 13 scanf("%lf",&a); 14 printf("请输入第二个数字:"); 15 scanf("%lf",&b); 16 if(0==b) //如果除数为0终止程序 17 exit(EXIT_FAILURE); 18 result=diva(a,b); 19 printf("相除的结果是: %.2lf\n",result); 20 return 0; 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> 3 void Exception(void) //注册终止函数,通过挂接到此函数,捕获异常信息 4 { 5 printf("试图去除以一个为0的数字,出现异常!\n"); 6 } 7 int main() 8 { 9 double a,b,result; 10 printf("请输入第一个数字:"); 11 scanf("%lf",&a); 12 printf("请输入第二个数字:"); 13 scanf("%lf",&b); 14 if(0==b) //如果除数为0终止程序 ,并挂接到模拟异常捕获的注册函数 15 { 16 17 atexit(Exception); 18 exit(EXIT_FAILURE); 19 } 20 result=diva(a,b); 21 printf("相除的结果是: %.2lf\n",result); 22 return 0; 23 }
这里需要注意的是,atexit()函数总是被执行的,就算没有exit()函数,当程序结束时也会被执行。并且,可以挂接多个注册函数,按照堆栈结构进行执行。abort()函数与exit()函数类似,当出错时,能使得程序正常退出,这里就不多说了。
2.使用assert()进行异常处理:
assert()是一个调试程序时经常使用的宏,切记,它不是一个函数,在程序运行时它计算括号内的表达式,如果表达式为FALSE (0), 程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。
另外需要注意的是:assert只有在Debug版本中才有效,如果编译为Release版本则被忽略。
我们就前面的问题,使用assert断言进行异常终止操作:构造可能出现出错的断言表达式:assert(number!=0)这样,当除数为0的时候,表达式就为false,程序报告错误,并终止执行。
代码如下:
1 代码 2 3 #include <stdio.h> 4 #include <assert.h> 5 double diva(double num1,double num2) //两数相除函数 6 { 7 double re; 8 re=num1/num2; 9 return re; 10 } 11 int main() 12 { 13 printf("请输入第一个数字:"); 14 scanf("%lf",&a); 15 printf("请输入第二个数字:"); 16 scanf("%lf",&b); 17 assert(0!=b); //构造断言表达式,捕获预期异常错误 18 result=diva(a,b); 19 printf("相除的结果是: %.2lf\n",result); 20 return 0; 21 }
3.使用errno全局变量,进行异常处理:
errno全局变量主要在调式中,当系统API函数发生异常的时候,将errno变量赋予一个整数值,根据查看这个值来推测出错的原因。
其中的各个整数值都有一个相应的宏定义,表示不同的异常原因:
1 代码 2 3 #define EPERM 1 /* Operation not permitted */ 4 #define ENOFILE 2 /* No such file or directory */ 5 #define ENOENT 2 6 #define ESRCH 3 /* No such process */ 7 #define EINTR 4 /* Interrupted function call */ 8 #define EIO 5 /* Input/output error */ 9 #define ENXIO 6 /* No such device or address */ 10 #define E2BIG 7 /* Arg list too long */ 11 #define ENOEXEC 8 /* Exec format error */ 12 #define EBADF 9 /* Bad file descriptor */ 13 #define ECHILD 10 /* No child processes */ 14 #define EAGAIN 11 /* Resource temporarily unavailable */ 15 #define ENOMEM 12 /* Not enough space */ 16 #define EACCES 13 /* Permission denied */ 17 #define EFAULT 14 /* Bad address */ 18 /* 15 - Unknown Error */ 19 #define EBUSY 16 /* strerror reports "Resource device" */ 20 #define EEXIST 17 /* File exists */ 21 #define EXDEV 18 /* Improper link (cross-device link?) */ 22 #define ENODEV 19 /* No such device */ 23 #define ENOTDIR 20 /* Not a directory */ 24 #define EISDIR 21 /* Is a directory */ 25 #define EINVAL 22 /* Invalid argument */ 26 #define ENFILE 23 /* Too many open files in system */ 27 #define EMFILE 24 /* Too many open files */ 28 #define ENOTTY 25 /* Inappropriate I/O control operation */ 29 /* 26 - Unknown Error */ 30 #define EFBIG 27 /* File too large */ 31 #define ENOSPC 28 /* No space left on device */ 32 #define ESPIPE 29 /* Invalid seek (seek on a pipe?) */ 33 #define EROFS 30 /* Read-only file system */ 34 #define EMLINK 31 /* Too many links */ 35 #define EPIPE 32 /* Broken pipe */ 36 #define EDOM 33 /* Domain error (math functions) */ 37 #define ERANGE 34 /* Result too large (possibly too small) */ 38 /* 35 - Unknown Error */ 39 #define EDEADLOCK 36 /* Resource deadlock avoided (non-Cyg) */ 40 #define EDEADLK 36 41 /* 37 - Unknown Error */ 42 #define ENAMETOOLONG 38 /* Filename too long (91 in Cyg?) */ 43 #define ENOLCK 39 /* No locks available (46 in Cyg?) */ 44 #define ENOSYS 40 /* Function not implemented (88 in Cyg?) */ 45 #define ENOTEMPTY 41 /* Directory not empty (90 in Cyg?) */ 46 #define EILSEQ 42 /* Illegal byte sequence */
这里我们就不以前面的除数为0的例子来进行异常处理了,因为我不知道如何定义自己特定错误的errno,如果哪位知道,希望能给出方法。我以一个网上的例子来说明它的使用方法:
1 代码 2 3 #include <errno.h> 4 #include <math.h> 5 #include <stdio.h> 6 int main(void) 7 { 8 errno = 0; 9 if (NULL == fopen("d:\\1.txt", "rb")) 10 { 11 printf("%d", errno); 12 } 13 else 14 { 15 printf("%d", errno); 16 } 17 return 0; }
这里试图打开一个d盘的文件,如果文件不存在,这是查看errno的值,结果是2、
当文件存在时,errno的值为初始值0。然后查看值为2的错误信息,在宏定义那边#define ENOFILE 2 /* No such file or directory */
便知道错误的原因了。
4.使用goto语句进行异常处理:
goto语句相信大家都很熟悉,是一个跳转语句,我们还是以除数为0的例子,来构造一个异常处理的例子:
1 代码 2 3 #include <stdio.h> 4 double diva(double num1,double num2) //两数相除函数 5 { 6 double re; 7 re=num1/num2; 8 return re; 9 } 10 int main() 11 { 12 int tag=0; 13 double a,b,result; 14 if(1==tag) 15 { 16 Throw: 17 printf("除数为0,出现异常\n"); 18 } 19 tag=1; 20 printf("请输入第一个数字:"); 21 scanf("%lf",&a); 22 printf("请输入第二个数字:"); 23 scanf("%lf",&b); 24 if(b==0) //捕获异常(或许这么说并不恰当,暂且这么理解) 25 goto Throw; //抛出异常 26 result=diva(a,b); 27 printf("%d\n",errno); 28 printf("相除的结果是: %.2lf\n",result); 29 30 return 0; 31 }
5.使用setjmp和longjmp进行异常捕获与处理:
setjmp和longjmp是非局部跳转,类似goto跳转作用,但是goto语句具有局限性,只能在局部进行跳转,当需要跳转到非一个函数内的地方时就需要用到setjmp和longjmp。setjmp函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。异常处理基本方法:
使用setjmp设置一个跳转点,然后在程序其他地方调用longjmp跳转到该点(抛出异常).
代码如下所示:
1 #include <stdio.h> 2 #include <setjmp.h> 3 jmp_buf j; 4 void Exception(void) 5 { 6 longjmp(j,1); 7 } 8 double diva(double num1,double num2) //两数相除函数 9 { 10 double re; 11 re=num1/num2; 12 return re; 13 } 14 int main() 15 { 16 double a,b,result; 17 18 19 printf("请输入第一个数字:"); 20 scanf("%lf",&a); 21 printf("请输入第二个数字:"); 22 if(setjmp(j)==0) 23 { 24 scanf("%lf",&b); 25 if(0==b) 26 Exception(); 27 result=diva(a,b); 28 printf("相除的结果是: %.2lf\n",result); 29 } 30 else 31 printf("试图除以一个为0的数字\n"); 32 return 0; 33 }
四 总结:
除了以上几种方法之外,另外还有使用信号量等等方法进行异常处理。当然在实际开发中每个人都有各种调式的技巧,而且这文章并不是说明异常处理一定要这样做,这只是对一般做法的一些总结,也不要乱使用异常处理,如果弄的不好就严重影响了程序的效率和结构,就像设计模式一样,不能胡乱使用。