setjmp()/longjmp()的使用方法和场合
setjmp和longjmp.为了让你实现复杂的流控制,程序在系统里面运行完全依靠内存(代码段,全局段,堆存储器,栈存储器)和寄存器的内容(栈指针,基地址,计数器),setjmp保存当前的寄存器里面的内容,longjmp是恢复这些内容.longjmp返回setjmp程序当前的状态.
#include < setjmp.h >
int setjmp(jmp_buf env);
保存当前寄存器的状态到env这个结构体里面.
这个结构体定义如下:
#define _JBLEN 9
typedef struct { int _jb[_JBLEN + 1]; } jmp_buf[1];
这是一个气人的方法,这是一个长度为_JBLEN + 1整型的数组,当调用setjmp的时候,存储寄存器的数字在这个数组里面.函数的返回值为0.
longjmp(jmp_buf env, int val);longjmp将数组里面存储的内容恢复到寄存器里面.但是longjmp没有返回值.与之相反的是,当调用它的时候,只要你在调用setjmp保存了env,就OK了.因为其他寄存器被存储,PC才被保存.Setjmp返回的值可以作为longjmp的参数val,但是不能为零.因此,如果setjmp返回非零值,并且返回值作为longjmp的参数,这样longjmp恢复的就是这个setjmp的环境.
这里有一个小例子:
#include < setjmp.h >
main()
{
jmp_buf env;
int i;
i = setjmp(env);
printf("i = %d\n", i);
if (i != 0) exit(0);
longjmp(env, 2);
printf("Does this line get printed?\n");
}
运行结果是:
UNIX> sj1
i = 0
i = 2
UNIX>
这里setjmp返回的值为0,因此我用2作为longjmp的参数.这个让代码从setjmp位置返回并且返回值为2.值被打印出,并且程序退出.
如果程序在反复或者递归调用的过程中被检测出来了某个错误,利用setjmp和longjmp程序能在高层次被很好的处理掉.看下面一个例子:
#include <setjmp.h>
#include <stdio.h>
int proc_4(jmp_buf env, int i)
{
if (i == 0) longjmp(env, 1);
return 14 % i;
}
int proc_3(jmp_buf env, int i, int j)
{
return proc_4(env, i) + proc_4(env, j);
}
int proc_2(jmp_buf env, int i, int j, int k)
{
return proc_3(env, i, k) + proc_3(env, j, k);
}
int proc_1(jmp_buf env, int i, int j, int k, int l)
{
return proc_2(env, i, j, k+1);
}
main(int argc, char **argv)
{
jmp_buf env;
int sj;
int i, j, k, l;
if (argc != 5) {
fprintf(stderr, "usage: sj2 i j k l\n");
exit(1);
}
sj = setjmp(env);
if (sj != 0) {
printf("Error -- bad value of i (%d), j (%d), k (%d), l (%d)\n",
i, j, k, l);
exit(0);
}
i = atoi(argv[1]);
j = atoi(argv[2]);
k = atoi(argv[3]);
l = atoi(argv[4]);
printf("proc_1(%d, %d, %d, %d) = %d\n", i, j, k, l, proc_1(env, i, j, k, l));
}
这个程序看起来是复杂的,但是实际上并不是这样.在这么复杂的调用程序的过程,到底什么发生了呢?proc_1调用了proc_4.如果proc_4的参数是0,调用longjmp将出现错误.否则处理正常.从这里看,如果你调用sj2传递非负参数,运行成功.如果传递参数全部是0,当你调用longjmp的时候,提示一个错误.
现在setjmp保存了所有的寄存器,包括sp和fp.这个意思就是调用setjmp之后,setjmp的env将不在可用.因为它存储了进程的sp和fp.当你恢复的时候,程序返回.堆栈是不同的状态和以前,并且你将有一个错误.请看例sj3.c
#include <setjmp.h> #include <stdio.h> int a(char *s, jmp_buf env) { int i; i = setjmp(env); printf("Setjmp returned -- i = %d, 0x%x\n", i, s); printf("s = %s\n", s); return i; } int b(int i, jmp_buf env) { printf("In B: i=%d. Calling longjmp(env, i)\n", i); longjmp(env, i); } main(int argc, char **argv) { jmp_buf env; if (a("Jim", env) != 0) exit(0); b(3, env); }
执行结果如下:
UNIX> sj3
Setjmp() returned -- i = 0
s = Jim
In B: i=3. Calling longjmp(env, i)
Setjmp() returned -- i = 3
Segmentation fault (core dumped)
UNIX>
当main第一次运行的时候,到底什么发生了.它的堆栈如下显示:
Stack
|-------------- |
| |
| |
| |
| |
| |
| | <-------- sp
| env[0] |
| env[1] |
| env[2] | pc = main
| env[3] |
| .... |
| env[8] |
| other stuff | <------- fp
|--------------- |
当main调用a()的时候,将反顺序将参数压入栈中,并且这个时候jsr被调用,压返回的ps和老的fp到堆栈中.fp和sp被改变为了建立一个空的堆栈为了调用a().
Stack
|---------------- |
| |
| | <--------- sp, fp
/------------- | old fp in main |
| | old pc in main |
| "Jim" <--- | s = "Jim" |
| /--- | pointer to env |
| \--> | env[0] |
| | env[1] |
| | env[2] | pc = a
| | env[3] |
| | .... |
| | env[8] |
\------------> | other stuff |
|--------------- |
首先a()分配空间给局部变量i.
Stack
|----------------|
| | <--------- sp
| i | <--------- fp
/------------- | old fp in main |
| | old pc in main |
| "Jim" <--- | s = "Jim" |
| /--- | pointer to env |
| \--> | env[0] |
| | env[1] |
| | env[2] | pc = a
| | env[3] |
| | .... |
| | env[8] |
\------------> | other stuff |
|--------------- |
这个时候调用setjmp,保存当前寄存器当前状态.总之,它保存当前的状态值sp,fp和pc.现在 a()打印"i=0",并且"s=jim",然后返回.现在堆栈和以前一样,除了env在a()内被机器的状态初始化了.
Stack
|----------------|
| |
| |
| |
| |
| |
| | <----------- sp
| env[0] |
| env[1] |
| env[2] | pc = main
| env[3] |
| .... |
| env[8] |
| other stuff | <------------ fp
|--------------- |
这时main调用b(),并且堆栈如下显示:
Stack
|----------------|
| |
| | <--------- sp, fp
/------------- | old fp in main |
| | old pc in main |
| | i = 3 |
| /--- | pointer to env |
| \--> | env[0] |
| | env[1] |
| | env[2] | pc = b
| | env[3] |
| | .... |
| | env[8] |
\------------> | other stuff |
|--------------- |
此时longjmp被调用,寄存器被恢复到a()调用setjmp的状态.并且pc返回到a函数中的setjmp.然而堆栈的值是和b()一样.
Stack
|----------------|
| | <--------- sp
| i = 2 | <--------- fp
/------------- | old fp in main |
| | old pc in main |
| | s?? = 3 |
| /--- | pointer to env |
| \--> | env[0] |
| | env[1] |
| | env[2] | pc = a
| | env[3] |
| | .... |
| | env[8] |
\------------> | other stuff |
|--------------- |
你应该看到这个问题.堆栈是在一个错误的状态.特别是a()中显示字符地址s,代替这个地址的数值是3.这样,当程序想打印出s的时候,程序尝试在内存位置3找到一个字符串,发生段错误.这是一个非常普便的bug,为了合适的用它们,你不能返回从调用了setjmp的程序中.