程序的非逻辑错误
程序出错大体可以分两种,一种是出错时,程序不会退出,可以继续往下执行,例如 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 功能,但是提供了 setjmp
和 longjmp
函数用于保存现场和恢复现场。先简单介绍下这两个函数:
所需头文件
#include <setjmp.h>
setjmp 和 longjmp 函数原型
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
setjmp
和 longjmp
函数在处理底层错误时非常有用。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 功能
可以看出,setjmp
和 longjmp
函数使用好了,可以实现代码的任意跳转,如果再加上信号处理函数,则完全可以实现类似 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
前缀的setjmp
和longjmp
函数,可以保存信号。如果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)