第17集全面了解setjmp与longjmp的使用
上一篇文章对setjmp函数与longjmp函数有了较全面的了解,尤其是这两个函数的作用,函数所完成的功能,以及将setjmp函数与 longjmp函数组合起来,实现异常处理机制时,程序模块控制流的执行过程等。这里更深入一步,将对setjmp与longjmp的具体使用方法和适用的场合,进行一个非常全面的阐述。
另外请特别注意,setjmp函数与longjmp函数总是组合起来使用,它们是紧密相关的一对操作,只有将它们结合起来使用,才能达到程序控制流有效转移的目的,才能按照程序员的预先设计的意图,去实现对程序中可能出现的异常进行集中处理。
与goto语句的作用类似,它能实现本地的跳转
这种情况容易理解,不过还是列举出一个示例程序吧!如下:
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(1) longjmp(mark, 1);
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(2) longjmp(mark, 2);
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(-1) longjmp(mark, -1);
// 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}
return;
}
上面的例程非常地简单,其中程序中使用到了异常处理的机制,这使得程序的代码非常紧凑、清晰,易于理解。在程序运行过程中,当异常情况出现后,控制流是进行了一个本地跳转(进入到异常处理的代码模块,是在同一个函数的内部),这种情况其实也可以用goto语句来予以很好的实现,但是,显然setjmp与 longjmp的方式,更为严谨一些,也更为友善。程序的执行流如图17-1所示。
setjmp与longjmp相结合,实现程序的非本地的跳转
呵呵!这就是goto语句所不能实现的。也正因为如此,所以才说在C语言中,setjmp与longjmp相结合的方式,它提供了真正意义上的异常处理机制。其实上一篇文章中的那个例程,已经演示了longjmp函数的非本地跳转的场景。这里为了更清晰演示本地跳转与非本地跳转,这两者之间的区别,我们在上面刚才的那个例程基础上,进行很小的一点改动,代码如下:
void Func1()
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(1) longjmp(mark, 1);
}
void Func2()
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(2) longjmp(mark, 2);
}
void Func3()
{
// 其它代码的执行
// 判断程序远行中,是否出现错误,如果有错误,则跳转!
if(-1) longjmp(mark, -1);
}
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
// 下面的这些函数执行过程中,有可能出现异常
Func1();
Func2();
Func3();
// 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}
return;
}
回顾一下,这与C++中提供的异常处理模型是不是很相近。异常的传递是可以跨越一个或多个函数。这的确为C程序员提供了一种较完善的异常处理编程的机制或手段。
setjmp和longjmp使用时,需要特别注意的事情
1、setjmp与longjmp结合使用时,它们必须有严格的先后执行顺序,也即先调用setjmp函数,之后再调用longjmp函数,以恢复到先前被保存的“程序执行点”。否则,如果在setjmp调用之前,执行longjmp函数,将导致程序的执行流变的不可预测,很容易导致程序崩溃而退出。请看示例程序,代码如下:
class Test
{
public:
Test() {printf("构造对象\n");}
~Test() {printf("析构对象\n");}
}obj;
//注意,上面声明了一个全局变量obj
void main( void )
{
int jmpret;
// 注意,这里将会导致程序崩溃,无条件退出
Func1();
while(1);
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
// 下面的这些函数执行过程中,有可能出现异常
Func1();
Func2();
Func3();
// 其它代码的执行
}
else
{
// 错误处理模块
switch (jmpret)
{
case 1:
printf( "Error 1\n");
break;
case 2:
printf( "Error 2\n");
break;
case 3:
printf( "Error 3\n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}
return;
}
上面的程序运行结果,如下:
构造对象
Press any key to continue
的确,上面程序崩溃了,由于在Func1()函数内,调用了longjmp,但此时程序还没有调用setjmp来保存一个程序执行点。因此,程序的执行流变的不可预测。这样导致的程序后果是非常严重的,例如说,上面的程序中,有一个对象被构造了,但程序崩溃退出时,它的析构函数并没有被系统来调用,得以清除一些必要的资源。所以这样的程序是非常危险的。(另外请注意,上面的程序是一个C++程序,所以大家演示并测试这个例程时,把源文件的扩展名改为 xxx.cpp)。
位律师回复
0条评论