Setjmp.h

setjmp.h是C标准函数库中提供“非本地跳转”的头文件：控制流偏离了通常的子程序调用与返回序列。互补的两个函数setjmp与longjmp提供了这种功能。 setjmp/longjmp的典型用途是异常处理机制的实现：利用longjmp恢复程序或线程的状态，甚至可以跳过栈中多层的函数调用。 setjmp保存当前的环境（即程序的状态）到平台相关的一个数据结构 (jmp_buf)，该数据结构在随后程序执行的某一点可被 longjmp用于恢复程序的状态到setjmp调用所保存到jmp_buf时的原样。这一过程可以认为是"跳转"回setjmp所保存的程序执行状态。setjmp的返回值指出控制是正常到达该点还是通过调用longjmp恢复到该点。因此有编程的惯用法: if( setjmp(x) ){/* handle longjmp(x) */}。 longjmp实现了非本地跳转，微软的IA32程序设计环境中正常的"栈卷回"("stack unwinding")因而没有发生，所以诸如栈中已定义的局部变量的析构函数的调用（用于销毁该局部变量）都没有执行。所有依赖于栈卷回调用析构函数所做的扫尾工作，如关闭文件、释放堆内存块等都没有做。但在微软的X64程序设计环境，longjmp启动了正常的"栈卷回"。 如果setjmp所在的函数已经调用返回了，那么longjmp使用该处setjmp所填写的对应jmp_buf缓冲区将不再有效。这是因为longjmp所要返回的"栈帧"(stack frame)已经不再存在了，程序返回到一个不再存在的执行点，很可能覆盖或者弄坏程序栈. #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被用于包住一个例外处理，类似try。longjmp调用类似于throw语句，允许一个异常返回给setjmp一个异常值。下属代码示例遵从1999 ISO C standard与Single UNIX Specification：仅在特定范围内引用setjmp if，switch或它们的嵌套使用的条件表达式 上述情况下与!一起使用或者与整数常值比较 作为单独的语句(不使用其返回值) 遵从上述规则使得创建程序环境缓冲区更为容易。更一般的使用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，使得进程知道该次系统调用失败。 ISO C标准要求setjmp必须是宏实现，但POSIX明确称未定义setjmp是宏实现还是函数实现。 Visual Studio 2008用法 GNU C 函式庫 2.7的用法 Microsoft Visual C++ 2010 x32或x64与Intel ICC 2011 (version 12) x32或x64，编译结果都是longjmp启动了正常的"栈卷回"。但GCC 4.4 x32版编译的longjmp不执行"栈卷回"。可见，是否“栈卷回”不具有移植性。 CS360 Lecture Notes — Setjmp and Longjmp. [2011-06-06]. （原始内容存档于2010-07-04）.  setjmp(3). [2011-06-06]. （原始内容存档于2009-07-26）.  Exceptions in C with Longjmp and Setjmp（页面存档备份，存于互联网档案馆）