代码改变世界

setjmp/longjmp 使用

2014-03-25 21:10  youxin  阅读(734)  评论(0编辑  收藏  举报

C语言中有一个goto语句,其可以结合标号实现函数内部的任意跳转(通常情况下,很多人都建议不要使用goto语句,因为采用goto语句后,代码维护工作量加大)。另外,C语言标准中还提供一种非局部跳转“no-local goto",其通过标准库<setjmp.h>中的两个标准函数setjmp和longjmp来实现。

C标准库<setjmp.h>

下面是K&R的《C程序设计语言(第2版 . 新版)》第232页给出的关于标准库<setjmp.h>的说明。

非局部跳转<setjmp.h>

头文件<setjmp.h>中的说明提供了一种避免通常的函数调用和返回顺序的途径,特别的,它允许立即从一个多层嵌套的函数调用中返回。

8.1 setjmp

#include <setjmp.h>
int setjmp(jmp_buf env);

setjmp()宏把当前状态信息保存到env中,供以后longjmp()恢复状态信息时使用。如果是直接调用setjmp(),那么返回值为0;如果是由于调用longjmp()而调用setjmp(),那么返回值非0。setjmp()只能在某些特定情况下调用,如在if语句、 switch语句及循环语句的条件测试部分以及一些简单的关系表达式中。

8.2 longjmp

#include <setjmp.h>
void longjmp(jmp_buf env, int val);

longjmp()用于恢复由最近一次调用setjmp()时保存到env的状态信息。当它执行完时,程序就象setjmp()刚刚执行完并返回非0值val那样继续执行。包含setjmp()宏调用的函数一定不能已经终止。所有可访问的对象的值都与调用longjmp()时相同,唯一的例外是,那些调用setjmp()宏的函数中的非volatile自动变量如果在调用setjmp()后有了改变,那么就变成未定义的。

 

cplusplus:

int setjmp (jmp_buf env);
Save calling environment for long jump

This macro with functional form fills env with information about the current state of the calling environment in that point of code execution, so that it can be restored by a later call to longjmp.

Calling longjmp with the information stored in env restores this same state and returns the control to that same point (the call to setjmp), which is evaluated as a particular non-zero value.

The state of the calling environment includes the values of all accessible objects, except those of automatic duration local to the function which do not have volatile-qualified types and which change before the call tolongjmp; these have indeterminate values.

The invocation of setjmp shall be an expression statement by itself, or be evaluated in a selection or iteration statement either as the (potentially negated) entire controlling expression or compared against an integer constant expression. Otherwise, it causes undefined behavior.

Parameters

env
Object of type jmp_buf where the environment information is stored.

 

Return Value

This macro may return more than once: A first time, on its direct invocation; In this case it always returns zero.
When longjmp is called with the information set to env, the macro returns again; this time it returns the value passed to longjmp as second argument if this is different from zero, or 1 if it is zero.

 

void longjmp (jmp_buf env, int val);
Long jump

Restores the environment to the state indicated by env, evaluating the setjmp expression that filled env as val.

The function never returns to the point where it has been invoked. Instead, the function transfers the control to the point where setjmp was last used to fill the env, and evaluates the whole expression as val (unless this is zero, in which case it evaluates as value of 1).

If env was not filled by a previous call to setjmp or if the function with such call has terminated execution, it causesundefined behavior.

In C++, the implementation may perform stack unwinding that destroys objects with automatic duration. If this invokes any non-trivial destructors, it causes undefined behavior.

Parameters

env
Object of type jmp_buf filled by a previous call to setjmp that contains information to restore the environment to that point.
val
Value to which the setjmp expression evaluates.
If this is zero, the expression evaluates as 1.

 

/* setjmp example: error handling */
#include <stdio.h>      /* printf, scanf */
#include <stdlib.h>     /* exit */
#include <setjmp.h>     /* jmp_buf, setjmp, longjmp */

main()
{
  jmp_buf env;
  int val;

  val = setjmp (env);
  if (val) {
    fprintf (stderr,"Error %d happened",val);
    exit (val);
  }

  /* code here */

  longjmp (env,101);   /* signaling an error */

  return 0;
}

The function uses setjmp to set up a catch-point that can later be called by longjmp to signal errors.

Output:

 

 error 101 happended

 

setjmp.hC标准函数库中提供“非本地跳转”的头文件控制流偏离了通常的子程序调用与返回串行。互补的两个函数setjmp与longjmp提供了这种功能。

 

setjmp/longjmp的典型用途是异常处理机制的实现:利用longjmp恢复程序或线程的状态,甚至可以跳过栈中多层的函数调用。

setjmp保存当前的环境(即程序的状态)到平台相关的一个数据结构 (jmp_buf),该数据结构在随后程序执行的某一点可被 longjmp用于恢复程序的状态到setjmp调用所保存到jmp_buf时的原样。这一过程可以认为是"跳转"回setjmp所保存的程序执行状态setjmp的返回值指出控制是正常到达该点还是通过调用longjmp恢复到该点。因此有编程的惯用法:

 if( setjmp(x) ){/* handle longjmp(x) */}

#include <stdio.h>
#include <setjmp.h>
 
static jmp_buf buf;
 
void second(void) {
    printf("second\n");         // 打印
    longjmp(buf,1);             // 跳回setjmp的调用处 - 使得setjmp返回值为1
}
 
void first(void) {
    second();
    printf("first\n");          // 不可能执行到此行
}
 
int main() {   
    if ( ! setjmp(buf) ) {
        first();                // 进入此行前,setjmp返回0
    } else {                    // 当longjmp跳转回,setjmp返回1,因此进入此行
        printf("main\n");       // 打印
    }
 
    return 0;
}

上述程序将输出:

second
main

注意到虽然first()子程序被调用,"first"不可能被打印。"main"被打印,因为条件语句if ( ! setjmp(buf) )被执行第二次。

更多:

http://zh.wikipedia.org/wiki/Setjmp.h

http://blog.codingnow.com/2010/05/setjmp.html