setjmp和longjmp的使用

来源:互联网 发布:自动播放软件 编辑:程序博客网 时间:2024/05/11 16:05

setjmp与logjmp包含在头文件/usr/include/setjmp.h中,使用前应在程序头部加入#include <setjmp.h>。

setjmp与longjmp结合使用时,它们必须有严格的先后执行顺序,也即先调用setjmp函数,之后再调用longjmp函数,以恢复到先前被保存的“程序执行点”。否则,如果在setjmp调用之前,执行longjmp函数,将导致程序的执行流变的不可预测,很容易导致程序崩溃而退出。

setjmp与longjmp的作用同goto语句类似,它能实现本地的跳转.

1.人们对于goto语句的忌讳,很多的专业书籍以及专业人士号召限制goto语句的使用,此时,setjmp与longjmp对goto语句有了很好的替代作用语句有一个局限性,它只能在函数内部跳转.而setjmp与longjmp可以在整个程序全局中跳转,实现"长跳转",弥补了goto功能的局限使用setjmp和longjmp可以捕捉程序中的异常,并采取异常处理机制.


setjmp设置jumper点,jumper是一个jmp_buf类型变量.在setjmp.h文件中有jmp_buf的定义,可见它是一个结构体数组.
/* Calling environment, plus possngibly a saved signal mask.  */

typedef struct __jmp_buf_tag    /* C++ doesn't like tagless structs.  */

{

/* NOTE: The machine-dependent definitions of `__sigsetjmp'

    assume that a `jmp_buf' begins with a `__jmp_buf' and that

    `__mask_was_saved' follows it.  Do not move these members

    or add others before it.  */

__jmp_buf __jmpbuf;     /* Calling environment.  */

    int __mask_was_saved;   /* Saved the signal mask?  */

    __sigset_t __saved_mask;    /* Saved signal mask.  */

调用该函数对env初始化,初始化后返回一个int值,第一次调用,这个int值为

函数原型:void longjmp(jmp_buf env, int val);

第二个参数:给setjmp(env)重新赋值,为val值

#include <stdio.h>

#include <setjmp.h>

void subroutine(void);

void subroutine_2(void);

jmp_buf jumper;

main()

{

    int value;

    int i = 0;

value = setjmp(jumper);   /* 设置jump点,初始化jumper,返回值0赋给value, */

:value = [%d]: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>/n",i,value);
    if(value == 0)
    {
        printf("[1]About to call subroutine...../n");
        subroutine();  /* 调转到subroutine()函数 */
        printf("Never go this..../n");
    }
    else if(value == 1)
    {
        printf("[2]About to call subroutine...../n");
        subroutine_2();  /* 调转到subroutine_2()函数 */
        printf("Never go this..../n");
    }
    else
    {
        printf("[3]Never go this..../n");
    }

    /* 调转到jumper初始化的地方,即setjmp(jumper)处,并将1赋给set(jumper) */
    longjmp(jumper,1);
    return;
}

    /* 调转到jumper初始化的地方,即setjmp(jumper)处,并将3赋给set(jumper) */
    longjmp(jumper,3);
    return;
}

三.使用setjmp,longjmp处理异常.
#include <stdio.h>
#include <setjmp.h>

jmp_buf jumper;
void exception();
int  deal_exception();

main()
{
     int value;
     int i = 0;

     value = setjmp(jumper);   /* 设置jump点,初始化jumper,返回值0赋给

     if ( 0 == value ) {
         exception();
     }
     else {
         switch ( value )
         {
            case 1:
                printf( "解决异常情况[%d]/n",value );
                break;
            case 2:
                printf( "解决异常情况[%d]/n",value );
                break;
            case 3:
                printf( "解决异常情况[%d]/n",value );
                break;
           default:
                printf( "异常情况[%d]未知/n",value );
                break;
        }
    }
}

void exception()
{
    int _err_no;

    if ( _err_no = 3 ) {
        printf("出现异常情况[%d]/n",_err_no);
        longjmp(jumper,_err_no);
    }

setjmp和longjmp使用时,需要特别注意的事情

  1、setjmp与longjmp结合使用时,它们必须有严格的先后执行顺序,也即先调用setjmp函数,之后再调用longjmp函数,以恢复到先前被保存的“程序执行点”。否则,如果在setjmp调用之前,执行longjmp函数,将导致程序的执行流变的不可预测,很容易导致程序崩溃而退出。请看示例程序,代码如下:

class Test
{
public:
Test()
~Test()
}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)。

  2、除了要求先调用setjmp函数,之后再调用longjmp函数(也即longjmp必须有对应的setjmp函数)之外。另外,还有一个很重要的规则,那就是longjmp的调用是有一定域范围要求的。这未免太抽象了,还是先看一个示例,如下:

int Sub_Func()
{
int jmpret, be_modify;

be_modify = 0;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
}
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;
}

//注意这一语句,程序有条件地退出
if (be_modify==0) exit(0);
}

return jmpret;
}

void main( void )
{
Sub_Func();

// 注意,虽然longjmp的调用是在setjmp之后,但是它超出了setjmp的作用范围。
longjmp(mark, 1);
}

  如果你运行或调试(单步跟踪)一下上面程序,发现它真是挺神奇的,居然longjmp执行时,程序还能够返回到setjmp的执行点,程序正常退出。但是这就说明了上面的这个例程的没有问题吗?我们对这个程序小改一下,如下:

int Sub_Func()
{
// 注意,这里改动了一点
int be_modify, jmpret;

be_modify = 0;

jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代码的执行
}
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;
}

//注意这一语句,程序有条件地退出
if (be_modify==0) exit(0);
}

return jmpret;
}

void main( void )
{
Sub_Func();

// 注意,虽然longjmp的调用是在setjmp之后,但是它超出了setjmp的作用范围。
longjmp(mark, 1);
}

  运行或调试(单步跟踪)上面的程序,发现它崩溃了,为什么?这就是因为,“在调用setjmp的函数返回之前,调用longjmp,否则结果不可预料” (这在上一篇文章中已经提到过,MSDN中做了特别的说明)。为什么这样做会导致不可预料?其实仔细想想,原因也很简单,那就是因为,当setjmp函数调用时,它保存的程序执行点环境,只应该在当前的函数作用域以内(或以后)才会有效。如果函数返回到了上层(或更上层)的函数环境中,那么setjmp保存的程序的环境也将会无效,因为堆栈中的数据此时将可能发生覆盖,所以当然会导致不可预料的执行后果。

   3、不要假象寄存器类型的变量将总会保持不变。在调用longjmp之后,通过setjmp所返回的控制流中,例程中寄存器类型的变量将不会被恢复。(MSDN中做了特别的说明,上一篇文章中,这也已经提到过)。寄存器类型的变量,是指为了提高程序的运行效率,变量不被保存在内存中,而是直接被保存在寄存器中。寄存器类型的变量一般都是临时变量,在C语言中,通过register定义,或直接嵌入汇编代码的程序。这种类型的变量一般很少采用,所以在使用setjmp和longjmp时,基本上不用考虑到这一点。

  4、MSDN中还做了特别的说明,“在C+ +程序中,小心对setjmp和longjmp的使用,因为setjmp和longjmp并不能很好地支持C++中面向对象的语义。因此在C++程序中,使用C++提供的异常处理机制将会更加安全。”虽然说C++能非常好的兼容C,但是这并非是100%的完全兼容。例如,这里就是一个很好的例子,在C++ 程序中,它不能很好地与setjmp和longjmp和平共处。在后面的一些文章中,有关专门讨论C++如何兼容支持C语言中的异常处理机制时,会做详细深入的研究,这里暂且跳过。

原创粉丝点击