上一节介绍了Linux操作系统中信号的概念。 我们知道崩溃错误通常伴随着信号的产生。 例如,分段错误引起的信号、除以0引起的信号等。
不仅如此,Linux中的信号还可以被“拦截”,甚至可以被修改来处理动作。 例如,上一节中,我们使用C语言编程来拦截信号,并将处理动作从默认的打印“错误”并退出更改为我们自定义的动作,因此程序在遇到分段错误时并没有退出。
try 语句中的错误处理
现在我们知道了 Linux 中信号的概念,我们实际上可以做一些更有趣的事情。 在讨论这个之前,我们先来看看当我们计算 8 除以 0 的值时会发生什么:
#encoding=utf8 if __name__=="__main__": a = 8/0 print a
果然,无论用什么工具来计算8除0,都一样没有意义。 同样的道理,除数遇到0就会崩溃退出:
# python2 t.py Traceback (most recent call last): File "t.py", line 4, ina = 8/0 ZeroDivisionError: integer division or modulo by zero
当然,在现实中,没有人会愚蠢到编写使用0作为除数的代码,但有时使用0作为除数是非常隐蔽的。 相信经常写代码的朋友一定都遇到过。
对于这种崩溃错误,try语句非常贴心地提供了。 现在使用try语句重写上面的代码,请看:
#encoding=utf8 if __name__=="__main__": try: a = 8/0 except Exception, e: print 'ERROR: ', e print "python exit normally"
当你执行代码时,你会发现虽然无法计算出8/0,但程序避免了崩溃。 它首先提示“ERROR:or by zero”,然后打印出“exit”信息,正常退出程序。
# python2 t.py ERROR: integer division or modulo by zero python exit normally
try 语法有助于编写更健壮的程序。 不幸的是,C语言没有这样的语法,但是在了解了Linux中信号的概念之后,我们可以自己用C语言实现一组try语句。
和功能
虽然C语言没有直接提供类似于中的try语句,但它确实提供了保存和恢复场景的功能。 结合我们已经了解的Linux中的信号机制,自己实现一套C语言的try语句并不容易。 多么困难的事情啊。
我们先看一下sum函数的说明:
并且函数非常适合解决低级错误或冲突。 它们可以保存堆栈上下文和环境表等信息。 稍后,函数可以使用这些信息,就像回到过去一样,修改程序的执行流程。 这话有点假。 让我们看一个例子。 请看下面的代码:
#include#include 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
事实上,真实和虚假的信息都被打印了。 真是令人惊讶,现在我们来分析一下这个程序:
现在应该清楚了,可能有的朋友发现了,将这两个函数与Linux中的信号处理函数结合起来,是不是可以实现类似中的try语句呢?
使用C语言创建类似的try语句
由于该函数可以回到过去并修改历史记录,因此实现 try 语句太简单了。 仍然以8除0的计算为例,我们可以编写如下代码:
编译执行后发现,C语言程序不仅没有崩溃退出,还输出了“main exit”信息,而且还贴心地提示了“ERROR: by Zero”错误信息。
# ./a.out ERROR: division by zero main exit normally
虽然功能大致实现了,但是上面的代码并不像try语法那么简洁。 这时候就可以利用宏封装对代码进行适当的调整。 请参见:
#include#include #include 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; }
现在封装好了,是不是很像里面的try语句? 编译并执行,结果符合预期:
# ./a.out ERROR: division by zero main exit normally
这样,我们就用C语言做了一个类似的try语句。 但它还是有局限性,比如genv全局变量的使用,而try只是简单的代码封装,不能嵌套。 再比如,这种封装会修改默认的信号处理功能,可能会给其他功能模块带来不便等,这些缺点当然是可以解决的。 由于篇幅限制,我们将在下一节或稍后继续讨论。
其实主要还是依赖栈的数据结构特性。 有兴趣的朋友可以自己先尝试一下,完全可以封装一个强大的C语言try库。
欢迎在评论区一起讨论、提问。 文章均为手写、原创。 他们每天深入浅出地介绍C语言、Linux等嵌入式开发。 如果你喜欢我的文章,就关注吧,你可以看到最新的更新和往期文章。