setjmp.h是C标准函数库中提供“非本地跳转”的头文件:控制流偏离了通常的子程序调用与返回序列。互补的两个函数setjmp与longjmp提供了这种功能。
setjmp/longjmp的典型用途是异常处理机制的实现:利用longjmp恢复程序或线程的状态,甚至可以跳过栈中多层的函数调用。
成员函数
告诫与限制
longjmp实现了非本地跳转,微软的IA32程序设计环境中正常的"栈解开"因而没有发生,所以诸如栈中已定义的局部变量的析构函数的调用(用于销毁该局部变量)都没有执行。所有依赖于栈解开调用析构函数所做的扫尾工作,如关闭文件、释放堆内存块等都没有做。但在微软的X64程序设计环境,longjmp启动了正常的"栈解开"。
如果setjmp所在的函数已经调用返回了,那么longjmp使用该处setjmp所填写的对应jmp_buf缓冲区将不再有效。这是因为longjmp所要返回的"栈帧"(stack frame)已经不再存在了,程序返回到一个不再存在的执行点,很可能覆盖或者弄坏程序栈.
使用例子
简单例子
#include
#include
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
#include
#include
#include
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,使得进程知道该次系统调用失败。
参考文献
外部链接
- [http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html Exceptions in C with Longjmp and Setjmp]
评论 (0)