setjmp()/longjmp()的使用方法和场合

来源:互联网 发布:制度矩阵 编辑:程序博客网 时间:2024/05/12 08:13

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的程序中.