异常简述(一):C语言中的异常处理机制

  人的一生会遇到很多大起大落,尤其是程序员.

  程序员写好的程序,论其消亡形式无非三种:无疾而终、自杀、他杀.

  当然作为一名程序员,最乐意看到自己写的程序能够无疾而终,因此尽快的学习异常处理机制是非常重要的!

  使自己的程序在遇到错误时能够克服错误,更健壮,而不是遇到错误就愤愤自杀.

  因此,在简述C++的异常机制之前,本文先来简述一下C语言中的异常处理机制.

  在C语言中,传统的错误处理方式有如下几种:

1.直接终止程序(自杀)

  例如:

?
1
2
3
4
5
6
int main(){
     int a = 10;
     int b = 20;
     int c = a/0;
     return 0;
}

  当用gcc编译完后,执行,会打印“浮点数例外”,然后程序结束.

  这种情况是不允许的,无条件终止程序的库无法运用到不能当机的程序里。

2.返回一个错误的值,附加错误码

  这种错误处理方式也很常见,比如我们用C语言打开一个文件失败时:

?
1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<errno.h>
int main(){
     FILE * fp = fopen ( "test.txt" , "r" );
     if (NULL == fp){
         printf ( "文件打开失败,错误码:%d\n" , errno );
     }
     return 0;
}

  因为我当前处于Linux系统下,没有GetLastError()函数,所以在Linux下使用全局变量errno来演示.

  这种情况,比较常用,但是有时不合适,例如返回错误码是int,每个调用都要检查错误值,极不方便,也容易让程序规模加倍

3.返回一个合法的值,让程序处于某种非法的状态

  这种例子,最常见的就是atoi函数,例如:

?
1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<stdlib.h>
int main(){
     int a = atoi ( "123456789" );
     int b = atoi ( "dasdasdcs" );
     printf ( "a = %d\n" ,a);
     printf ( "b = %d\n" ,b);
     return 0;
}

  虽然b为0,但是有一个全局变量表示了这个0属于非法值,不是由字符串0转过来的.

  这种情况,很容易误导调用者,万一调用者没有去检查全局变量errno或者通过其他方式检查错误,那是一个灾难,而且这种方式在并发的情况下不能很好工作

4.调用一个预先准备好在出现"错误"的情况下使用的函数.

  这种异常处理情况比较少见,那我就现场直编了一个:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<stdlib.h>
void DealError(){
     printf ( "除数为0,老兄你在逗我?\n" );
}
typedef void (*fun)();
int Div( int a, int b,fun callback){
     if (b==0){
         callback();
         return 0;
     }
     return a/b;
}
 
int main(){
     printf ( "正常情况下的4/2 = %d\n" ,Div(4,2,DealError));
     printf ( "调用错误处理函数的4/0 = %d\n" ,Div(4,0,DealError));
     return 0;
}

 5、通过暴力的方式解决

  暴力的方式有两种,abort()函数和常见exit()函数.例如依然处理除0问题,代码可以这样写:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
#include<stdlib.h>
int Div( int a, int b){
     if (b==0){
         //exit(1);  //直接退出
         abort ();    //在Windows下会弹出一个信息框
     }
     return a/b;
}
 
int main(){
     printf ( "正常情况下的4/2 = %d\n" ,Div(4,2));
     printf ( "调用错误处理函数的4/0 = %d\n" ,Div(4,0));
     return 0;
}

 6、使用goto语句

  虽然,goto语句十分强大,但违背了程序的顺序执行,打乱的程序的执行流,盲目的使用goto语句可能会出现意想不到的错误,因此,并不推荐使用goto语句.

  但,尽管如此,为了探索C语言的异常处理机制,我还是实现一下goto语句处理异常的代码.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<stdlib.h>
int main(){
     int a = 0;
     int b = 0;
     printf ( "请输入两个值:\n" );
     printf ( "a = " );
     scanf ( "%d" ,&a);
     printf ( "b = " );
     scanf ( "%d" ,&b);
     if (b==0){
         goto Error;
     }
     printf ( "a/b = %d\n" ,a/b);
     return 0;
Error:
     printf ( "除数不能为0,程序异常退出!\n" );
     exit (-1);
}

 

 7、使用setjmp()与longjmp()

  goto语句虽然可以跳来跳去,但标记与goto必须处于同一作用域内.

  想从一个函数跳转到另一个函数,就必须使用setjmp与longjmp组合.

  实例如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<setjmp.h>
 
jmp_buf mark;
 
int Div( int a, int b){
     if (b==0){
         longjmp (mark,1);   //会使state = 1
     }
     return a/b;
}
int main(){
     int State = setjmp (mark);   //保存寄存器相关信息,初始值为0
     if (State==0){
         Div(4,0);
     } else {
         switch (State){
             case 1:
                 printf ( "除0异常!\n" );
         }
     }
     return 0;
}

注意事项:

  1、setjmp必须先调用,在异常位置通过调用longjmp以恢复先前被保存的程序执行点,否则将导致 不可预测的结果,甚至程序崩溃。

  2、在调用setjmp的函数返回之前调动longjmp,否则结果不可预料。

setjmp与longjmp存在以下缺陷:

  1、函数的使用者必须非常靠近函数调用的地方编写错误处理代码,无疑使代码变的臃肿笨拙。

  2、setjmp()和longjmp()并不能够很好的支持C++面向对象的语义。

  以上情况,便是C语言中的异常处理常见的机制,C++提供了更为完善的异常处理机制,我将在下文中简述.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值