许多读者在此之前或许底子没有运用或许听说过C言语的反常处理,印象中都是C++或许java才有的东西,C言语怎样会有反常处理呢?当然估量在大学出于一般的性的学习考试之类的话教师几乎是不会提及C言语的反常处理的,那么究竟什么是反常处理?C言语中又该怎样来完成反常处理呢?那么咱们今日就解说一种典型的完成C言语反常处理的办法,以setjmp()函数和longjmp()函数完成的反常处理,我尽或许的把它们是怎样完成反常处理办法解说清楚,期望接下来的内容对你有所协助,让你学到一些新的东西。
首要咱们来了解下反常处理,反常是一个在程序履行期间产生的事情,它中止正在履行的程序的正常的指令流,而咱们的反常处理功用供给了处理程序运转时呈现的任何意外或反常情况的办法。
接下来咱们先看看setjmp()函数和longjmp()函数完成C言语反常处理。
setjmp()函数原型:
int ( jmp_buf env );
假如咱们翻开源代码会发现在setjmp()函数中涉及到许多的寄存器的操作,如Ebp、Ebx、Edi、Esi、Esp、 Eip等等,在此就不一一例举了,咱们无非是想向读者阐明一个问题,那便是在调用setjmp()函数的过程中保存程序的当时运转时的仓库环境,保存这些仓库环境有什么用呢?接下来咱们看看longjmp()函数。
longjmp()函数原型:
void longjmp( jmp_buf env, int value );
刚刚上面的函数功用是保存程序履行时分的仓库环境,咱们发现在longjmp()函数里也有一个jmp_buf类型的env变量,这其实是为了确保接下来调用longjmp时,会依据这个从前保存的变量来康复从前的环境,而且当时的程序操控流,会因此而回来到开始调用setjmp()函数时的程序履行点。此刻,在接下来的操控流的例程中,所能拜访的一切的变量,包含了longjmp函数调用时所具有的变量。咱们就这样说读者或许就得有点笼统了,那咱们仍是来看看一段代码后再来剖析吧,在此特别给出了一个简略的代码,由易到难的来剖析。
[cpp] view plaincopy#include
#include
jmp_buf buf;
void error_code(void)
{
longjmp(buf,1);
}
int main()
{
double a,b;
printf("请输入被除数:");
scanf("%lf",&a);
printf("请输入除数:");
if(setjmp(buf)==0)
{
scanf("%lf",&b);
if(0==b)
error_code();
printf("相除的成果为:%f\n",a/b);
}
else
printf("呈现过错除数为0\n");
return 0;
}
运转成果为:
[cpp] view plaincopy请输入被除数:12
请输入除数:0
呈现过错除数为0
Press any key to continue
看了上面的运转成果,现在咱们接着上面的讲,在一开始的部分咱们并没有详细的告知setjmp()函数和longjmp()函数的回来值和参数的详细意义。两个函数中的env变量保存的是调用setjmp()函数的时分当时运转程序的仓库信息,而longjmp()函数的调用便是依据在调用setjmp()函数的时分的仓库信息回来到开始调用setjmp()函数的当地,而其间的第二个参数便是此刻setjmp()函数的回来值,可是值得留意的便是调用longjmp()函数之后setjmp函数回来的值有必要对错零值,假如longjmp传送的value参数值为0,那么实际上setjmp回来的值是1。一开始咱们调用setjmp()函数的时分,它的回来值为0,之后再调用longjmp()函数的时分,经过设定longjmp()函数的第二个参数来设定它的回来值。
现在咱们来剖析上边的代码,在main()函数中,咱们开始调用setjmp()函数的时分,把当时的环境信息保存在了buf中,函数回来0,然后往下运转,咱们输入0。经过if句子发现b的值为0那么就调用error_code()函数来进行处理,在该函数中咱们运用了longjmp()函数,其运用办法为longjmp(buf,1);,经过上面的解说,咱们知道榜首个参数的作用是用来得到开始调用setjmp()函数是的环境信息,以便在运用longjmp()函数的时分可以正确的回来到setjmp()函数开始的调用途,然后面的参数表明的回来到setjmp()函数的时分的回来值。咱们在此回来1,所以履行else部分的句子。
剖析完了上面的代码,读者应该都知道了两个函数的运用办法,值得留意的当地便是咱们在setjmp与longjmp结合运用时,它们有必要有严厉的先后履行次序,先调用setjmp函数,之后再调用longjmp函数,以康复到从前被保存的“程序履行点”。不然,假如在setjmp调用之前,履行longjmp函数,将导致程序的履行流变的不行猜想,很容易导致程序溃散而退出。为了加深读者的关于两个函数参数的运用,咱们看看下面的代码:
[cpp] view plaincopy#include
#include
#include
#include
jmp_buf buf;
void func1()
{
longjmp(buf,1);
}
void func2()
{
longjmp(buf,2);
}
void func3()
{
longjmp(buf,3);
}
int main( void )
{
int value;
char str[50];
value = setjmp( buf );
if( value == 0 )
{
func1();
}
switch( value )
{
case 1:
strcpy( str, "func1 return value" );
break;
case 2:
strcpy( str, "func2 return value" );
break;
case 3:
strcpy( str, "func3 return value" );
break;
default:
strcpy( str, "Other error value" );
break;
}
printf("%s:%d\n",str,value);
if(1==value)
{
func2();
}
if(2==value)
{
func3();
}
return 0;
}
运转成果为:
[cpp] view plaincopyfunc1 return value:1
func2 return value:2
func3 return value:3
Press any key to continue
看看运转成果,咱们剖析下代码,在每个函数中咱们调用longjmp()函数,经过设置第二个参数为不同的值来改动setjmp()函数的回来值,然后咱们经过判别value值来打印出是那个函数的回来值,咱们在此例举这个简略的代码是要咱们加深关于这两个函数的参数的运用情况。假如咱们在上面的代码中稍作修正,在setjmp()函数的调用之前调用longjmp()函数,咱们发现此刻没有任何的输出,程序直接溃散掉退出了。
接下来咱们来看看一个函数的运用,假如关于这个函数不了解的读者,可以多看几回我给出的模仿该函数的完成代码。
头文件: #include
功用:设置某一信号的对应动作
函数原型:void (*signal(int signum,void(* handler)(int)))(int);
留意:榜首个参数signum指明晰所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
假如读者是榜首场触摸上面的函数的话或许有些不知道该怎样着手,一时间有些难以了解,不知道究竟是什么意思。别急,咱们现在来逐个剖析它究竟是什么意思,咱们在解说之前再来看看它的别的一种表明办法。
typedef void(*sig_t) ( int );
sig_t signal(int signum,sig_t handler);
把上面的函数原型拆分为了如上两行代码,现在咱们剖析下上面的两行代码。
榜首行代码界说了一个函数指针(注:假如有对函数指针知识点不熟悉的读者可以去阅览我之前写的那篇文章《C言语的那些小秘密之函数指针》),其类型为含有一个int型参数,无回来值;
第二行代码中,signal函数的回来值是一个函数指针,与榜首行咱们界说的类型相同,第二个参数也为一个函数指针,其实signal的回来值便是第二个函数指针指向的函数地址。这样说或许有不少读者都有些懵的感觉,仍是老办法,代码最有说服力,咱们仍是为读者模仿下signal的完成办法,呈现出一段代码来剖析下。
[cpp] view plaincopy#include
#include
typedef void (*pfun) ();
pfun signal_call(int a,pfun fdsa);
pfun signal_call(int a,pfun fdsa)
{
return fdsa;
}
void func()
{
printf("hello world!!!\n");
}
int main()
{
pfun p = func;
signal_call(1,p)();
return 0;
}
运转成果为:
[cpp] view plaincopyhello world!!!
Press any key to continue
现在咱们来剖析下上面的代码,咱们选用上面的界说方式完成了如下两行代码:
typedef void (*pfun) ();
pfun signal_call(int a,pfun fdsa);
在接下来的main()函数中咱们界说了一个函数指针p,使其指向了 func()函数,接下来咱们运用了一句 signal_call(1,p)();代码,完成了func函数调用,那么这究竟是怎样完成的呢?那么咱们来剖析下,前面的signal_call(1,p)回来的是一个函数指针,在代码中咱们发现其实回来的便是p,所以signal_call(1,p)();就可以变形为p(),看到这种方式咱们这就可以很清楚的看出,它调用的便是咱们代码中的func()函数了。现在读者理解了signal()函数的完成办法,接下来咱们来看看一段运用signal捕捉除数为0时分的反常代码。
[cpp] view plaincopy#include
#include
#include
#include
#include
#include
jmp_buf buf;
int err;
void handler( int num )
{
err = num;
printf( "产生浮点核算反常\n");
longjmp( buf, 1);
}
int main( void )
{
double a, b;
char str[20];
int ret;
_control87( 0, _MCW_EM );
if( signal( SIGFPE, handler ) == SIG_ERR )
{
printf("绑定失利\n" );
abort();
}
ret = setjmp( buf );
if(0 == ret )
{
printf("请输入被除数:");
scanf("%lf",&a);
printf("请输入除数:");
scanf("%lf",&b);
printf( "a / b = %4.3g\n", a/b);
printf("产生反常时分不会被履行的句子\n");
}
return 0;
}
没有产生反常时分的运转成果:
[cpp] view plaincopy请输入被除数:123
请输入除数:3
a / b = 41
产生反常时分不会被履行的句子
Press any key to continue
产生反常时分的运转成果:
[cpp] view plaincopy请输入被除数:12
请输入除数:0
产生浮点核算反常
Press any key to continue
现在来剖析下上面的运转成果,先看看_control87( 0, _MCW_EM );这句,或许许多读者关于这代码比较生疏,它的功用是敞开一切的浮点核算反常,通常情况下浮点核算反常是被屏蔽掉的,咱们为了可以使得接下来的signal可以捕捉到浮点核算反常,所以要将其敞开。在往下看咱们经过signal( SIGFPE, handler )来绑定了一个浮点核算反常处理函数,假如产生反常时,那么就调用handler()函数来处理。接下来经过ret = setjmp( buf );保存程序运转的环境信息,以便接下来的调用longjmp()函数可以依据这个保存的信息回来该程序从前setjmp()函数的履行点。一起咱们比照两次运转的成果发现假如发现反常的时分接下来的打印句子“printf("产生反常时分不会被履行的句子\n");”是不会被履行的,直接跳转到咱们绑定的handler()函数履行了,当然咱们在此仅仅是例举一些简略的代码教会读者学会运用setjmp()函数和longjmp()函数来完成反常处理,读者完全可以在此基础上编写出杂乱的反常处理。
到这儿C言语的反常处理部分就完毕了,因为自己水平有限,博客中的不当或过错之处在所难免,殷切期望读者批评指正。一起也欢迎读者一起讨论相关的内容,假如愿意沟通的话请留下你名贵的定见。