For faster navigation, this Iframe is preloading the Wikiwand page for setjmp.h.

setjmp.h

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

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

成员函数

int setjmp(jmp_buf env) 建立本地的jmp_buf缓冲区并且初始化,用于将来跳转回此处。这个子程序[1] 保存程序的调用环境于env参数所指的缓冲区,env将被longjmp使用。如果是从setjmp直接调用返回,setjmp返回值为0。如果是从longjmp恢复的程序调用环境返回,setjmp返回非零值。
void longjmp(jmp_buf env, int value) 恢复env所指的缓冲区中的程序调用环境上下文,env所指缓冲区的内容是由setjmp子程序[1]调用所保存。value的值从longjmp传递给setjmplongjmp完成后,程序从对应的setjmp调用处继续执行,如同setjmp调用刚刚完成。如果value传递给longjmp零值,setjmp的返回值为1;否则,setjmp的返回值为value

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

成员类型

jmp_buf 数组类型,例如struct int[16][2]struct __jmp_buf_tag[3],用于保存恢复调用环境所需的资讯。

告诫与限制

longjmp实现了非本地跳转,微软的IA32程式设计环境中正常的"栈卷回"("stack unwinding")因而没有发生,所以诸如栈中已定义的局部变量的析构函数的调用(用于销毁该局部变量)都没有执行。所有依赖于栈卷回调用析构函数所做的扫尾工作,如关闭文件、释放堆内存块等都没有做。但在微软的X64程式设计环境,longjmp启动了正常的"栈卷回"。[4]

如果setjmp所在的函数已经调用返回了,那么longjmp使用该处setjmp所填写的对应jmp_buf缓冲区将不再有效。这是因为longjmp所要返回的"栈帧"(stack frame)已经不再存在了,程序返回到一个不再存在的执行点,很可能覆盖或者弄坏程序栈.[5][6]

使用例子

简单例子

#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) )被执行第二次。

异常处理

在下例中,setjmp被用于包住一个例外处理,类似trylongjmp调用类似于throw语句,允许一个异常返回给setjmp一个异常值。下属代码示例遵从1999 ISO C standardSingle UNIX Specification:仅在特定范围内引用setjmp

  • ifswitch或它们的嵌套使用的条件表达式
  • 上述情况下与!一起使用或者与整数常值比较
  • 作为单独的语句(不使用其返回值)

遵从上述规则使得创建程序环境缓冲区更为容易。更一般的使用setjmp可能引起未定义行为,如破坏局部变量;编译器被要求保护或警告这些用法。但轻微的复杂用法如switch ((exception_type = setjmp(env))) { }在文献与实践中是常见的,并保持了相当的可移植性。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
 
void first(void);
void second(void);
 
/* This program's output is:
 
calling first
calling second
entering second
second failed with type 3 exception; remapping to type 1.
first failed, exception type 1
 
*/
 
/* Use a file scoped static variable for the exception stack so we can access
 * it anywhere within this translation unit. */
static jmp_buf exception_env;
static int exception_type;
 
int main() {
    void *volatile mem_buffer;
 
    mem_buffer = NULL;
    if (setjmp(exception_env)) {
        /* if we get here there was an exception */
        printf("first failed, exception type %d\n", exception_type);
    } else {
        /* Run code that may signal failure via longjmp. */
        printf("calling first\n");
        first();
        mem_buffer = malloc(300); /* allocate a resource */
        printf("%s",strcpy((char*) mem_buffer, "first succeeded!")); /* ... this will not happen */
    }
    if (mem_buffer)
        free((void*) mem_buffer); /* carefully deallocate resource */
    return 0;
}
 
void first(void) {
    jmp_buf my_env;
 
    printf("calling second\n");
    memcpy(my_env, exception_env, sizeof(jmp_buf));
    switch (setjmp(exception_env)) {
        case 3:
            /* if we get here there was an exception. */
            printf("second failed with type 3 exception; remapping to type 1.\n");
            exception_type = 1;

        default: /* fall through */
            memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
            longjmp(exception_env, exception_type); /* continue handling the exception */

        case 0:
            /* normal, desired operation */
            second();
            printf("second succeeded\n");  /* not reached */
    }
    memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
}
 
void second(void) {
    printf("entering second\n" ); /* reached */
    exception_type = 3;
    longjmp(exception_env, exception_type); /* declare that the program has failed */
    printf("leaving second\n"); /* not reached */
}

用于信号处理

信号处理机制中,进程在检查收到的信号,会从原来的系统调用中直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就是通过使用setjmp和longjmp来实现的。setjmp将保存的上下文加载用户空间,并继续在旧的上下文中继续执行。这就是说,进程执行一个系统调用,当因为资源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核为进程将现在的上下文切换成原先通过setjmp调用保存在进程用户区的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使得进程知道该次系统调用失败。

参考文献

  1. ^ 1.0 1.1 ISO C标准要求setjmp必须是宏实现,但POSIX明确称未定义setjmp是宏实现还是函数实现。
  2. ^ Visual Studio 2008用法
  3. ^ GNU C 函式库 2.7的用法
  4. ^ Microsoft Visual C++ 2010 x32或x64与Intel ICC 2011 (version 12) x32或x64,编译结果都是longjmp启动了正常的"栈卷回"。但GCC 4.4 x32版编译的longjmp不执行"栈卷回"。可见,是否“栈卷回”不具有移植性。
  5. ^ CS360 Lecture Notes — Setjmp and Longjmp. [2011-06-06]. (原始内容存档于2010-07-04). 
  6. ^ setjmp(3). [2011-06-06]. (原始内容存档于2009-07-26). 

外部链接

{{bottomLinkPreText}} {{bottomLinkText}}
setjmp.h
Listen to this article

This browser is not supported by Wikiwand :(
Wikiwand requires a browser with modern capabilities in order to provide you with the best reading experience.
Please download and use one of the following browsers:

This article was just edited, click to reload
This article has been deleted on Wikipedia (Why?)

Back to homepage

Please click Add in the dialog above
Please click Allow in the top-left corner,
then click Install Now in the dialog
Please click Open in the download dialog,
then click Install
Please click the "Downloads" icon in the Safari toolbar, open the first download in the list,
then click Install
{{::$root.activation.text}}

Install Wikiwand

Install on Chrome Install on Firefox
Don't forget to rate us

Tell your friends about Wikiwand!

Gmail Facebook Twitter Link

Enjoying Wikiwand?

Tell your friends and spread the love:
Share on Gmail Share on Facebook Share on Twitter Share on Buffer

Our magic isn't perfect

You can help our automatic cover photo selection by reporting an unsuitable photo.

This photo is visually disturbing This photo is not a good choice

Thank you for helping!


Your input will affect cover photo selection, along with input from other users.

X

Get ready for Wikiwand 2.0 🎉! the new version arrives on September 1st! Don't want to wait?