执行非局部跳转
setjmp() 和 longjmp()
- 在一个深度嵌套的函数调用中发生了错误,需要放弃当前任务,从多层函数调用中返回
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
setjmp()调用为后续longjmp()调用的执行确立了跳转目标. 该目标正是程序发起setjmp()调用的位置,
- 可以区分 setjmp 调用是初始返回还是第二次“返回”。初始调用返回值为 0,后续“伪”返回的返回值为 longjmp()调用中 val 参数所指定的任意值。
- 若指定的val为0,则函数会将二次返回的值调换为1;
setjmp()直接调用返回0 ;若从longjmp()处返回则返回longjmp()中指定的val.
使用longjmp()的第二个参数的原因是可以多个longjmp()对应一个setjmp()
- 若指定的val为0,则函数会将二次返回的值调换为1;
#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
- longjmp必须在setjmp调用之后,而且longjmp必须在setjmp的作用域之内。具体来说,在一个函数中使用setjmp来初始化一个全局标号,然后只要该函数未曾返回,那么在其它任何地方都可以通过longjmp调用来跳转到 setjmp的下一条语句执行。实际上setjmp函数将发生调用处的局部环境保存在了一个jmp_buf的结构当中,只要主调函数中对应的内存未曾释放 (函数返回时局部内存就失效了),那么在调用longjmp的时候就可以根据已保存的jmp_buf参数恢复到setjmp的地方执行。
setjmp()函数的使用限制
-SUSv3和C99规定,对setjmp()的调用只能在如下语境中使用
1. if、switch、while等整个控制表达式.
2. 作为一元操作符! 的操作对象,其最终表达式构成了选择或迭代语句的整个控制表达式
3. 作为比较操作的一部分,另一操作对象必须是一个常量表达式
4. 作为独立的函数调用,没有嵌入更大的表达式
- int a = setjmp(env);/*WRONG!*/
之所以规定这些限制,是因为作为常规函数的 setjmp()实现无法保证拥有足够信息来保存所有寄存器值和封闭表达式中用到的临时栈位置,以便于在 longjmp()调用后此类信息能得以正确恢复。因此,仅允许在足够简单且无需临时存储的表达式中调用 setjmp()。
滥用longjmp()
如果将 env 缓冲区定义为全局变量对所用函数可见 , 那么就可以执行如下操作.
1. 调用函数x(),使用setjmp()调用在全局变量env中建立一个跳转目标
2. 从函数x()中返回.
3. 调用函数y(),使用env变量调用longjmp()函数
这是一个严重错误,因为 longjmp()调用不能跳转到一个已经返回的函数中。思考一下,在这种情况下,longjmp()函数会对栈打什么主意—尝试将栈解开,恢复到一个不存在的栈帧位置,这无疑将引起混乱。如果幸运的话,程序会一死(crash)了之。然而,取决于栈的状态,也可能会引起调用与返回间的死循环,而程序好像真地从一个当前并未执行的函数中返回了。(在多线程程序中有与之相类似的滥用,在线程某甲中调用 setjmp()函数,却在线程某乙中调用 longjmp()。)
done