- 成员函数
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 传递给setjmp 。longjmp 完成后,程序从对应的setjmp 调用处继续执行,如同setjmp 调用刚刚完成。如果value 传递给longjmp 零值,setjmp 的返回值为1;否则,setjmp 的返回值为value 。 |
setjmp
保存当前的环境(即程序的状态)到平台相关的一个数据结构 (jmp_buf
),该数据结构在随后程序执行的某一点可被longjmp
用于恢复程序的状态到setjmp
调用所保存到jmp_buf
时的原样。这一过程可以认为是"跳转"回setjmp
所保存的程序执行状态。setjmp
的返回值指出控制是正常到达该点还是通过调用longjmp
恢复到该点。因此有编程的惯用法:if( setjmp(x) ){/* handle longjmp(x) */}
。
- 成员类型
| 数组类型,例如 |
- 告诫与限制
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
被用于包住一个例外处理,类似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(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 */
}