linux c语言编程,使用setjmp和longjmp函数自制类似python的try-catch模块捕获异常,处理段错误,浮点错误等信号错误(1)

程序的非逻辑错误

程序出错大体可以分两种,一种是出错时,程序不会退出,可以继续往下执行,例如 open 文件失败,也只是无法打开文件而已,程序还可以执行我们设定的错误处理语句,比如打印出错误信息等。还有一种错误发生时,程序直接就退出了,例如非法操作指针引起的段错误,把 0 做被除数的浮点错误等。

先来看一段 c 代码,代码很简单,就是计算 8 除以 0 的值,传递给 a。显然,任何数除以 0 都是无法计算的,所以编译的时候,编译器会发出警告:

#include <stdio.h>

int main()
{
    int a = 8/0;
    return 0;
}
$ gcc t.c
t.c: In function ‘main’:
t.c:5:14: warning: division by zero [-Wdiv-by-zero]
     int a = 8/0;
              ^

出现警告,我们当然可以立刻修改代码。可是,有时候 0做被除数不是显式的,此时编译器就发现不了这种错误了,当然不会再给出警告,例如下面这段代码:

#include <stdio.h>

int main()
{
    int b = 1;
    int c = 1;
    int a = 8/(b-c);
    return 0;
}

编译之,发现没有警告,执行时却报错了,程序直接退出了。如下:

$ gcc t.c
$ ./a.out 
Floating point exception (core dumped)

python 处理错误的 try 语句

再来看看使用 python 代码重写的代码,同样计算 8 除以 0 的值,传递给 a。

#encoding=utf8

if __name__=="__main__":
    try:
        a = 8/0
    except Exception, e:
        print 'ERROR: ', e

我们执行之,发现错误被 try 语句捕获了,打印信息如下:

$ python t.py 
ERROR integer division or modulo by zero

错误发生后,程序没有直接退出,执行了我们安排的错误打印语句,这就让程序的稳定性更加强了。

C 语言实现逻辑跳转

c 语言没有直接提供类似 python 的 try 功能,但是提供了 setjmplongjmp 函数用于保存现场和恢复现场。先简单介绍下这两个函数:

所需头文件

#include <setjmp.h>

setjmp 和 longjmp 函数原型

int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

setjmplongjmp 函数在处理底层错误时非常有用。longjmp函数可以恢复setjmp保存的现场,简单来说就是使用 longjmp 函数可以跳转到setjmp函数处。setjmp函数首次成功执行时返回 0,通过longjmp函数返回到setjmp后,setjmp返回值等于longjmp的参数二val

请看下面这段代码:

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>

int main()
{
    jmp_buf mark;
    int ret = 0;

    ret = setjmp(mark);
    if(0==ret){
        printf("ret == 0 is true\n");   
        longjmp(mark, -1);
    }else{
        printf("ret == 0 is false\n");
    }

    return 0;
}

编译之,执行得如下结果:

$ ./a.out 
ret == 0 is true
ret == 0 is false

首次到达 setjmp 函数时,ret 等于 0,因此打印了“ret == 0 is true”,紧接着longjmp函数得以执行,程序又返回到setjmp,只不过这次 ret 等于 longjmp 函数的第二个参数,即 ret 等于 -1,接着,"ret == 0 is false"就被打印出来了。

C 语言实现类似 python 的 try 功能

可以看出,setjmplongjmp 函数使用好了,可以实现代码的任意跳转,如果再加上信号处理函数,则完全可以实现类似 python 的 try 功能。请看下面这段代码:

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>

jmp_buf mark;

void sigHandle(int sig)
{
    siglongjmp(mark, -1);
}

int main()
{
    int ret = 0, a = 0;

    signal(SIGFPE, sigHandle);

    ret = sigsetjmp(mark, 1);
    if(0==ret){
        a = 8/0;
        printf("ret == 0 is true\n");   
    }else{
        printf("ERROR: division by zero\n");
    }

    return 0;
}

我们知道,在 linux 系统中,如果出现 0 做被除数的错误,则会收到 SIGFPE 信号,我们这里指定处理该信号的函数为 sigHandle 函数,这样就可以在出现错误时,重新跳转回 sigsetjmp 函数,根据返回值的不同,就可以执行错误打印函数了。带sig前缀的setjmplongjmp函数,可以保存信号。如果sigsetjmp函数的第二个参数不是 0,那么程序的 signal mark 将会被保存下来。

编译上面的代码,执行结果如下:

$ ./a.out 
ERROR: division by zero

程序执行到 a = 8/0; 语句时,出现浮点错误,程序进入 sigHandle 函数,跳回sigsetjmp函数,返回值变为 -1,此时“ERROR: division by zero”就被打印出来了。这样,我们的 C 语言也有类似于 python 的 try 功能了。

上述代码的缺点和为解决的问题

上面一节的代码,虽然成功模拟了 python 的 try 功能,但是有两个致命问题:

  • 覆盖了原有的信号处理函数。
  • 信号处理函数不能传递参数,因此只能使用全局变量,但是这样一来,模拟 try 功能就不能支持嵌套使用了。

下一节,将解决这两个问题。
下一节:linux c语言编程,使用setjmp和longjmp函数自制类似python的try-catch模块捕获异常,建立栈数据结构,实现多级嵌套使用(2)

阅读更多:   C语言 , Linux笔记
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif