原创文章,转载请注明出处,谢谢!
作者:清林,博客名:飞空静渡
我们知道,在c语言中我们可以使用goto语句在一个函数中进行跳转,例如一个常用的goto方式为:
......
//分配资源
......
if error1
goto error;
if error2
goto error;
........
error:
......
// 释放资源
......
但是goto语句也只限于一个函数内,不能进行函数间的跳转。在c语言中,进行函数间的跳转使用setjmp和longjmp函数。
为什么要有个函数间的跳转呢,这是因为,如果我们在调用函数时,函数的调用的嵌套的层次很深的话,如果出错,那么一层一层的返回和判断就很麻烦,所以,如果出错,就可以直接返回到最上面的调用的函数就会很方便。
我们来看一下这两个函数怎么使用,首先看一下这两个函数的原型:
#include <setjmp.h>
int setjmp(jmp_buf env); //直接调用则返回0,如从longjmp调用则返回非0
int longjmp(jmp_buf env, int val);
jum_buf是一个类型,其中env存储了一些longjmp调用返回用来恢复栈状态的所有信息。longjmp中的env和setjmp中的env是同一个。val是程序员自定义的直,这个值用在setjmp的返回值中,这样我们就可以知道是在哪个的longjmp跳转回来的。另外,由于在不同的函数间调用setjmp和longjmp,而这两个函数要公用一个env变量,所以把env定义为一个全局变量。
下面我们看一个简单的例子:
#include <stdio.h> #include <setjmp.h> jmp_buf jmpbuffer; void func1(); void func2(); int main(void) { printf("in main function!/n"); if(setjmp(jmpbuffer) != 0) { printf("get the error from jump return!/n"); return -1; } func1(); printf("main function end!/n"); return 0; } void func1() { printf("in func1 function!/n"); func2(); } void func2() { printf("in func2 function!/n"); longjmp(jmpbuffer, 1); }
编译:gcc main.c
运行:./a.out
输出:
in main function!
in func1 function!
in func2 function!
get the error from jump return!
我们在程序中可以看到,我们在main函数用setjmp中设置了一个接受跳转返回的点,在func2函数中用longjmp跳转返回。
在跳转返回后,我们直接退出函数,那么后面的
printf("main function end!/n");
return 0;
这两个语句将得不到执行。
前面我们说过longjmp函数中的val参数用来確定我们的jump是在哪产生的,下面我们修改一下前面的例子,看看怎样区别不同的跳转点。
#include <stdio.h> #include <setjmp.h> jmp_buf jmpbuffer; void func1(); void func2(); int main(void) { printf("in main function!/n"); int errNum = 0; errNum = setjmp(jmpbuffer); if(errNum != 0) { if (errNum == 1) printf("get the error from func1 function !/n"); if (errNum == 2) printf("get the error from func2 function !/n"); return -1; } func1(); printf("main function end!/n"); return 0; } void func1() { printf("in func1 function!/n"); longjmp(jmpbuffer, 1); func2(); } void func2() { printf("in func2 function!/n"); longjmp(jmpbuffer, 2); }
编译:gcc main.c
运行:./a.out
输出:
in main function!
in func1 function!
get the error from func1 function !
注意:不可这样
if(setjmp(jmpbuff) == 1)
........
if(setjmp(jmpbuff) == 2)
........
这样会在main中设置两个跳转返回点,而且是同一个jmpbuff,那么前面一个会被后面一个覆盖,则用func1中的跳转返回点就是后面的那个,在main函数中就会比较返回值是否为2,因为在func1中跳转返回是1,因此会再次执行后面的func1函数,那么就会陷入死循环中了。
我们知道,在一个进程中,当我们调用一个函数时,我们的进程就会在栈中分配一个新栈区给这个函数,当这个函数返回时,就销毁这个栈区。
因此,当我们进行函数间的跳转时,我们上层的函数的某些变量的值是得不到保存的。我们可以看下是哪些变量受到影响。
我们修改第一个例子:
#include <stdio.h> #include <setjmp.h> jmp_buf jmpbuffer; void func1(int, int, int, int); void func2(); int globVal; int main(void) { int autoVal; register int regiVal; volatile int volaVal; static int statVal; globVal = 1, autoVal = 2, regiVal = 3, volaVal = 4, statVal = 5; if(setjmp(jmpbuffer) != 0) { printf("get the error from jump return!/n"); printf("globVal = %d, autoVal = %d, regiVal = %d, volaVal = %d, statVal = %d/n", globVal, autoVal, regiVal, volaVal, statVal); return -1; } globVal = 101, autoVal = 102, regiVal = 103, volaVal = 104, statVal = 105; func1(autoVal, regiVal, volaVal, statVal); printf("main function end!/n"); return 0; } void func1(int i, int j, int k, int l) { printf("in func1 function!/n"); printf("globVal = %d, autoVal = %d, regiVal = %d, volaVal = %d, statVal = %d/n", globVal, i, j, k, l); func2(); } void func2() { printf("in func2 function!/n"); longjmp(jmpbuffer, 1); }
第一次,我们尝试不优化编译:
编译:gcc main.c
运行:./a.out
输出:
in func1 function!
globVal = 101, autoVal = 102, regiVal = 103, volaVal = 104, statVal = 105
in func2 function!
get the error from jump return!
globVal = 101, autoVal = 102, regiVal = 103, volaVal = 104, statVal = 105
第二次,我们进行优化编译:
编译:gcc -O main.c
运行:./a.out
输出:
in func1 function!
globVal = 101, autoVal = 102, regiVal = 103, volaVal = 104, statVal = 105
in func2 function!
get the error from jump return!
globVal = 101, autoVal = 2, regiVal = 3, volaVal = 104, statVal = 105
我们可以看到,全局、静态和易失性变量不受优化的影响,它们的值是最近变化的值。这也告诉我们如果我们写一个非局部跳转的程序,就应该使用volatile属性的变量。