揭开setjmp/longjmp的迷雾

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

 

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

 

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

 

一、setjmplogjmp的使用场合:

1、人们对于goto语句的忌讳,很多的专业书籍以及专业人士号召限制goto 句的使用,此时,setjmplongjmpgoto语句有了很好的替代作用.

2goto语句有一个局限性,它只能 在函数内部跳转.setjmplongjmp可以在整个程序全局中跳转,实现"长跳转",弥补了goto功能的局限.

3、使用setjmplongjmp可以捕捉程序中的异常,并采取 异常处理机制.

 

二、使用setjmp设置跳转点,longjmp回到原设置点

setjmplongjmp必须结合起来使用;

函数原型:int setjmp(jmp_buf env);

setjmp(env):设置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.  */

} jmp_buf[1];

 

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

 

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

 

第一个参数:setjmp(env)设置的jumper.

第二个参数: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, */

 

i++;

printf("执行第[%d]: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");

}

    return 0;

}

 

void subroutine(void)

{

/* 调转到jumper初始化的地方,setjmp(jumper),并将1赋给set(jumper) */

longjmp(jumper,1);

return;

}

 

void subroutine_2(void)

{  

/* 调转到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赋给value, */

 

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);

}

return;

}

 

以上为本人学习setjmplongjmp后所做的总结,做为新手,文中例子并不能最好的表现使用setjmplongjmp后的优势,但也能说明这两个函数如何使用.文中如有不当之处,也欢迎批评指正.

 

转自:http://hi.baidu.com/_%C5%CE%C8%FD%C4%EA_/blog/item/c4d95d12b331f25cf819b882.html

 

setjmp 的正确使用

 

setjmp C 语言解决 exception 的标准方案。我个人认为,setjmp/longjmp 这组 api 的名字没有取好,导致了许多误解。名字体现的是其行为:跳转,却没能反映其功能:exception 的抛出和捕获。

 

longjmp 从名字上看,叫做长距离跳转。实际上它能做的事情比名字上看起来的要少得多。跳转并非从静止状态的代码段的某个点跳转到另一个位置(类似在汇编层次的 jmp 指令做的那样),而是在运行态中向前跳转。C 语言的运行控制模型,是一个基于栈结构的指令执行序列。表示出来就是 call / return :调用一个函数,然后用 return 指令从一个函数返回。setjmp/longjmp 实际上是完成的另一种调用返回的模型。setjmp 相当于 call longjmp 则是 return

 

重要的区别在于:setjmp 不具备函数调用那样灵活的入口点定义;而 return 不具备 longjmp 那样可以灵活的选择返回点。其次,第一、setjmp 并不负责维护调用栈的数据结构,即,你不必保证运行过程中 setjmp longjmp 层次上配对。如果需要这种层次,则需要程序员自己维护一个调用栈。这个调用栈往往是一个 jmp_buf 的序列;第二、它也不提供调用参数传递的功能,如果你需要,也得自己来实现。

 

以库形式提供的 setjmp/longjmp 和以语言关键字 return 提供的两套平行的运行流控制放在一起,大大拓展了 C 语言的能力。把 setjmp/longjmp 嵌在单个函数中使用,可以模拟 pascal 中嵌套函数定义:即在函数中定义一个局部函数。ps. GNUC 扩展了 C 语言,也在语法上支持这种定义方法。这种用法可以让几个局部函数有访问和共享 upvalue 的能力。把 setjmp/longjmp 放在大框架上,则多用来模拟 exception 机制。

 

setjmp 也可以用来模拟 coroutine 。但是会遇到一个难以逾越的难点:正确的 coroutine 实现需要为每个 coroutine 配备一个独立的数据栈,这是 setjmp 无法做到的。虽然有一些 C coroutine 库用 setjmp/longjmp 实现。但使用起来都会有一定隐患。多半是在单一栈上预留一块空间,然后给另一个 coroutine 运行时覆盖使用。当数据栈溢出时,程序会发生许多怪异的现象,很难排除这种溢出 bug 。要正确的实现 coroutine ,还需要 setcontext ,这已经不是 C 语言的标准库了。

 

在使用 setjmp 时,最常见的一个错误用法就是对 setjmp 做封装,用一个函数去调用它。比如:

 

int try(breakpoint bp)

{

    return setjmp(bp->jb);

}

 

void throw(breakpoint bp)

{

    longjmp(bp->jb,1);

}

setjmp 不应该封装在一个函数中。这样写并不会引起编译错误。但十有八九会引起运行期错误。错误的起源在于 longjmp 的跳转返回点,必须在运行流经过并有效的位置。而如果对 setjmp 做过一层函数调用的封装后。上例中的 setjmp 设置的返回点经过 try 的调用返回后,已经无效。如果要必要封装的话,应该使用宏。

 

setjmp/longjmp 对于大多数 C 程序员来说比较陌生。正是在于它的定义含糊不清,不太容易弄清楚。使用上容易出问题,运用场合也就变的很狭窄,多用于规模较大的库或框架中。和 C++ 语言提供的 execption 机制一样,很少有构架师愿意把它暴露到外面,那需要对二次开发的程序员有足够清晰的头脑,并充分理解其概念才不会用错。这往往是不可能的。

 

另外,setjmp/longjmp 的理念和 C++ 本身的 RAII 相冲突。虽然许多编译器为防止 C++ 程序员错误使用 setjmp 都对其做了一定的改进。让它可以正确工作。但大多数情况下,还是在文档中直接声明不推荐在 C++ 程序中使用这个东西。

 

btw,关于 RAII ,的确是个好东西。但和诸多设计模式一样,不是真理。如果你是一个从 C++ 进化来的 C 程序员,则更应该警惕思维的禁锢,RAII 是一种避免资源泄露的好方案,但不是唯一方案。

 

转自:http://linode.codingnow.com/cgi-bin/mt/mt-tb.cgi/563

 

 

你如何区别从setjmp函数的两种不同返回方式呢?

 

当setjmp函数第1次被调用时,它返回0。当setjmp作为longjmp的执行结果再次返回时,它的返回值是longjmp的第2个参数,它必须是个非0值。通过检查它的返回值,程序可以判断是否调用了longjmp。如果存在多个longjmp,也可以由此判断哪个longjmp被调用。

 

 

何时使用非本地跳转?

 

如果存在一长串的函数调用链,即使只有最深层的那个函数发现了错误,调用链中的所有函数都必须返回并检查错误代码。在这种情况下使用setjmp和longjmp去除了中间函数的错误代码逻辑,从而对他们进行了简化。

你可能感兴趣的:(exception,语言,pascal,Signal,subroutine,RAII)