该 API 的作用与 goto 类似,但 goto 是本地的,只能跳转到函数内部的标记上,而不能将控制权转移到程序的任何地点。
C 语言为了解决该限制,提供了 setjmp 和 longjmp。声明在 setjmp.h 头文件中,且包含了 jmp_buf 的声明。原理是:
1. setjmp(jmp_buf buf) 设置 jump 点,向 buf 中填充程序存放位置、栈、寄存器等数据。填充完毕后,setjmp 返回 0;
2. 以后调用 longjmp(buf, return),其效果就是一个非局部的 goto,即跳转到设定 buf 的 setjmp 处。当作为长跳转的目标而被调用时,setjmp 返回 return 值或者 1(如果此时 return 被设置成 0,则返回 1,即,此时 setjmp 不能返回 0).
事实上,setjmp/longjmp 更像是 come from,即从哪里来,而不是 goto,即到哪里去。
setjmp/longjmp 在 C++ 中变异为更普通的 catch/throw 异常处理机制。
setjmp/longjmp 是通过操纵“过程活动记录”实现的。关于“过程活动记录”及 setjmp/longjmp 的实现原理,尚待进一步研究。
man setjmp 中提到:
setjmp() saves the stack context/environment in buf for later use by longjmp().The stack context will be invalidated if thefunction which called setjmp() returns.
所以,对 setjmp 的调用不能额外的再封装一层函数,例如:
int try(breakpoint bp) {
return setjmp(bp->jb);
}
此时,尽管编译不会受到影响,但是会引起运行错误。
回想“过程活动记录”,记录的是函数返回地址、参数等信息,如果在 setjmp() 外面再包一层,那么记录的函数地址就不是想跳转的函数了。
Code Sample:
#include <setjmp.h>
jmp_buf buf;
void test(void) {
printf("in test()\r\n");
longjmp(buf, 1);
printf("u'll never c this\r\n");
}
int main(int argc, char *argv[]) {
/* 第一次调用返回 0,执行 else 分支;longjmp 调用后,跳转到此处,返回 longjmp 中设定的 1,于是打印 "back in main" */
if (setjmp(buf)) {
printf("back in main\r\n");
} else {
printf("first time\r\n");
test();
}
}
PS:去了解一下 C++ 的 RAII