linux下C语言开发18,使用C语言自制try模块

上一节介绍了 linux 操作系统中信号的概念,我们知道了崩溃性错误通常会伴随着信号的产生。例如段错误引发的 SIGSEGV 信号,0 做除数引发的 SIGFPE 信号等等。

不仅如此,linux 中的信号也是可以被“截获”的,甚至还能够被修改处理动作。例如上一节,我们使用了 C语言编程截获了 SIGSEGV 信号,并将处理动作由默认的打印“segmentation fault”并退出,修改为我们自定义的动作,所以程序在遇到段错误时也没有退出。

python 中的错误处理—— try 语句

知道了 linux 中信号的概念,其实我们可以做些更好玩的事,在讨论这个之前,先来看看使用 python 计算 8 除以 0 的值会怎样:

#encoding=utf8

if __name__=="__main__":
        a = 8/0
        print a

不出所料,无论使用何种工具计算 8 除以 0 都是一样的没有意义,python 也是如此,在遇到 0 做除数也会崩溃退出:

# python2 t.py 
Traceback (most recent call last):
  File "t.py", line 4, in <module>
    a = 8/0
ZeroDivisionError: integer division or modulo by zero

当然,现实中谁也不会白痴的写出 0 做除数的代码,但是有些时候 0 做除数是非常隐蔽的,相信常写代码的朋友一定遇到过。

针对这类崩溃性错误,python 非常贴心的提供了 try 语句。现在使用 try 语句改写一下上面的代码,请看:

#encoding=utf8

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

    print "python exit normally"


执行代码,会发现虽然仍无法计算 8/0,但是程序却避免了崩溃,先是提示“ERROR: integer division or modulo by zero”,然后打印出了“python exit normally”的程序正常退出信息。

# python2 t.py 
ERROR:  integer division or modulo by zero
python exit normally

python 的 try 语法有利于写出更加健壮的程序。但是遗憾的是 C语言并没有这样的语法,不过在了解了 linux 中信号的概念之后,我们完全可以自己实现一套 C语言的 try 语句。

setjmp 和 longjmp 函数

C 语言虽然没有直接提供类似于 python 中的 try 语句,但是却提供了 setjmp 和 longjmp 函数用于保存和恢复现场,再结合我们已经了解的 linux 中的信号机制,自己来实现一套 C语言 try 语句也并不是什么难事。

先来看看 setjmp 和 longjmp 函数的说明:

setjmp 和 longjmp 函数非常适合解决底层的错误或者冲突,setjmp 可以保存栈上下文和环境表等信息,之后 longjmp 函数可以使用这些信息,就像时光倒流回到过去一样,修改程序的执行流程。这么说有些虚,来看一下实例,请看下面的代码:

#include <stdio.h>
#include <setjmp.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;
}

分析一下这个程序,会打印“ret == 0 is true”,还是“ret == 0 is false”呢?编译并执行,却得到如下结果:

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


居然真假两条信息都被打印出来了。真叫人吃惊,现在来分析一下这个程序:

  • setjmp 函数第一次执行时,会将程序执行到这句所产生的现场环境保存到 mark 里,接着返回 0。
  • 因为 ret 为 0,所以程序打印出了ret == 0 is true”。
  • 接着执行了 longjmp 函数,它会恢复 mark 保存的现场,也就是程序又跳回 setjmp 这一行了,但是 longjmp 函数小小的修改了一点历史:将 setjmp 的返回值修改为 -1 了。
  • 因为这次 ret 为 -1,所以程序又打印了“ret == 0 is false”。
  • 程序执行了 return 0。

现在应该都清楚了,可能已经有朋友发现了,把这两个函数和 linux 中的信号处理函数结合起来,不就实现了类似 python 中的 try 语句吗?

使用C语言,自制类似 python 的 try 语句

既然 longjmp 函数能够回到过去修改历史,那实现 try 语句就太简单了。还是以计算 8 除以 0 为例,我们可以写如下代码:

编译并执行,发现 C语言程序不但没有崩溃退出,输出了“main exit normally”信息,而且还贴心的提示了“ERROR: division by zero”错误提示信息。

# ./a.out 
ERROR: division by zero
main exit normally

虽然功能大体实现了,但是上面的代码并不像 try 语法那么简洁。这时可以借助于 define 宏封装,现在将代码做适当调整,请看:

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
jmp_buf   genv;

#define        try         if(({           \
                            int __ret = 0;  \
                            __ret = sigsetjmp(genv,1);  \
                            0==__ret;   \
                        }))
#define        except      else

void signal_handle(int sig)
{
    siglongjmp(genv, -1);
}
int main()
{
    int ret = 0, a = 0;
    signal(SIGFPE, signal_handle);

    try{
        a = 8/0;
    }except{
        printf("ERROR: division by zero\n");
    }

    printf("main exit normally\n");

    return 0;
}


现在经过封装,是不是很像 python 中的 try 语句了呢?编译执行,结果和预期一样:

# ./a.out 
ERROR: division by zero
main exit normally

这样我们就使用C语言自制了类似python的try语句。不过它还是有局限的,例如使用了 genv 全局变量,以及 try 只是简单的代码封装,不能嵌套使用。再例如,这种封装会修改默认的信号处理函数,可能会为其他功能模块带来不便,等等。这些不足当然是能够解决的,限于篇幅,下一节或以后再继续讨论。

其实主要就是借助于栈的数据结构特点,感兴趣的朋友可以自己先试一试,完全可以封装一个强大的 C语言 try 库。
阅读更多:   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