C语言的本地跳转是指goto语句,但是这个语句最大局限就是只能实现函数内部的跳转。所以非本地跳转就是解决这个问题。非本地跳转是由setjmp和longjmp来完成的。
setjmp函数的原型是int setjmp(jmp_buf env),longjmp函数的原型是void long_jmp(jmp_buf env, int value)。这两个函数都是包含在头文件setjmp.h中。setjmp函数主要用来保存当前执行状态,作为后续跳转的目标。调用setjmp时,当前状态会被存放在env指向的结构中,这个env将被 long_jmp 操作作为参数,以返回调用点,跳转的结果看起来就好像刚从setjmp返回一样,所以这个保存状态的jmp_buf变量env一般定义为全局变量。setjmp第一次调用setjmp的时候返回值为0;而从long_jmp操作返回时,返回值是由longjmp传入的参数value决定。通过判断setjmp的返回值,就可以判断当前返回的状态。
#include <stdio.h>
#include <setjmp.h>
jmp_buf jmpbuffer;
static void print_0(int i);
static void print_1(int i);
static void print_2(int i);
static void print_3(int i);
int main(){
int i = 0;
int value = 0;
int flag;
flag = setjmp(jmpbuffer);
if(flag == 0){
print_0(i);
} else if(flag == 1){
print_1(i);
} else if ((flag == 2)){
print_2(i);
} else{
print_3(i);
}
return 1;
}
static void print_0(int i){
printf("print_0 start : i = %d\n", i);
i += 10;
printf("print_0 end : i = %d\n", i);
longjmp(jmpbuffer, 1);
printf("print_0 return");
}
static void print_1(int i){
printf("print_1 start : i = %d\n", i);
i += 11;
printf("print_1 end : i = %d\n", i);
longjmp(jmpbuffer, 2);
printf("print_1 return");
}
static void print_2(int i){
printf("print_2 start : i = %d\n", i);
i += 12;
printf("print_2 end : i = %d\n", i);
longjmp(jmpbuffer, 3);
printf("print_2 return");
}
static void print_3(int i){
printf("print_3 start : i = %d\n", i);
i += 13;
printf("print_3 end : i = %d\n", i);
printf("print_3 return\n");
}
上面的函数进行了几次跳转,由最初的setjmp返回0开始,然后是longjmp返回1,2,每次longjmp之后,函数的运行状态回到了最初setjmp保存的那个状态,这个可以由下面的输出的i的值看出。
$ ./test7-10
print_0 start : i = 0
print_0 end : i = 10
print_1 start : i = 0
print_1 end : i = 11
print_2 start : i = 0
print_2 end : i = 12
print_3 start : i = 0
print_3 end : i = 13
print_3 return
总结:setjmp和longjmp的用法其实很简单,用通俗的话来理解这两个函数:这两个函数就是提供一种功能,能够在setjmp的时候将当前的运行情况进行保存,然后在后来的某个时间点遇到了些问题就可以通过之前保存的状态恢复到setjmp那个点。同时也提供通过检查setjmp不同返回值的情况检查当前的运行情况。我个人认为这个与函数调用没有什么区别,栈是用来保存函数调用的状态然后返回时恢复状态,只不过setjmp和longjmp可以跨越好长的函数调用栈直接回去。最后就是setjump保存的是栈的状态,那么这种情况下对于自动变量等都是不保证这个值是setjmp是的值还是longjmp调用时候的值。