setjmp和longjmp解析

来源:互联网 发布:光头成男脸型数据 编辑:程序博客网 时间:2024/06/06 01:32

借助于库函数setjmp()longjmp()可执行非局部跳转(nonlocal goto)。非局部跳转的含义就是跳转到当前执行函数之外的某个位置。

C中还有个著名的goto语句,当时老师说到这个语句时就是强调在结构化程序中最好不用goto语句,但是在实际工作中,需要错误处理时,goto语句还是经常派上用场的,但是goto语句存在一个限制,即不能从当前函数跳转到另一个函数。

错误处理中经常出现如下的场景:在一个深度嵌套的函数调用中出现了错误,需要放弃当前任务,从多层函数调用中返回,并在高层函数中继续执行。当然,我们可以通过一层层返回状态值,再层层处理。但是如果可以从嵌套函数中直接跳出,返回到调用者,编码会更为简单。

假设如下的函数调用

A->B->C->D->E

A调用B,依次调用到函数E,每一层函数调用都会产生函数的栈帧,保存函数调用的参数,返回值等等。假设E中发生问题,只要A还没有返回,longjmp可以使函数直接回到A,而不用层层返回。


函数说明:

#include <setjmp.h>int setjmp(jmp_buf env);void longjmp(jmp_buf env, int val);

setjmp.h是C标准库提供的头文件,提供了非局部跳转函数setjmplongjmp
setjmp()调用为后续由longjmp()调用执行的跳转确立了跳转目标。相当于goto语句里跳转到的lable

第一次发起setjmp()调用时,返回值为0。通过查看这个值,可以区分setjmp是初始返回还是第二次返回。setjmp后面的返回值由longjmp中的val值来指定,以区分程序中跳转至同一目标的不同起跳位置。一般即时设置val的值为0,也会自动替换为1,以区别于初始调用。

setjmp将当前的状态信息保存到env中,这样longjmp跳转回来时就可以恢复系统的各种状态信息。longjmp中使用的env应与setjmp中的保持一致。一般定义成全局变量。

env除了保存当前进程的其它信息,还保存程序计数寄存器和栈指针寄存器的副本。主要是后续调用longjmp()时完成两个步骤的操作:

  • 将发起longjmp()调用的函数和之前调用setjmp()的函数之间的函数栈帧从栈上剥离。又叫解开栈。因为env中保存了栈指针,所以这里把系统栈指针的值重置为一开始env里保存的栈指针值就行了。
  • 重置程序计数器,这样程序就从初始的setjmp()位置开始重新执行。因为env里也保存了设置setjmp时的程序计数器的值的。

Sample解析

#include <setjmp.h>#include <stdlib.h>static jmp_buf env;static voidf2(void){    longjmp(env, 2);}static voidf1(int argc){    if (argc == 1)        longjmp(env, 1);    f2();}intmain(int argc, char *argv[]){    switch (setjmp(env)) {    case 0:     /* This is the return after the initial setjmp() */        printf("Calling f1() after initial setjmp()\n");        f1(argc);               /* Never returns... */        break;                  /* ... but this is good form */    case 1:        printf("We jumped back from f1()\n");        break;    case 2:        printf("We jumped back from f2()\n");        break;    }    exit(EXIT_SUCCESS);}

不带参数执行:

$ ./longjmpCalling f1() after initial setjmp()We jumped backed from f1()

带参数执行:

$ ./longjmp xCalling f1() after initial setjmp()We jumped backed from f2()

这里的参数x只是用来改变程序接受的argc的值的。

初次switch时,setjmp(env)值都为0,所以走case 0分支,输出”Calling f1() after initial setjmp() “,
接下来执行f1()。
不带参数时,argc为1,所以执行 longjmp(env, 1), 这时候 setjmp(env)也返回1,走case1分支,打印“We jumoed backed from f1()”,程序结束。
带参数x时,argc为2,所以在f1()中,函数直接执行f2(),longjmp(env, 1),setjmp(env)返回2,走case 2,打印 “We jumped backed from f2()”程序结束。

0 0
原创粉丝点击