setjmp()和longjmp()函数

之前我们讲到了过程活动记录(AR),那么如何来操纵AR呢,一个可能的方法是,根据局部变量的地址进行推算,例如对于上面的a函数,执行a函数时的当前AR地址就是参数i的地址偏移8个字节,也就是 ((char*)&i) - 8。

然而,不同的C编译器,以及不同的硬件平台都会产生不同的AR结构布局,甚至在一些平台上,AR根本不会存放到Stack中。所以这种方式操纵AR是不通用的。

为此,C语言通过库函数的方式提供了操纵AR的统一方法,那就是setjmp和longjmp函数。

(注意:goto语句不能跳出C语言当前的函数

1. 作用:

setjmp()和longjmp() 可以实现非局部控制转移即从一个函数到另外一个函数的跳转。

2. 函数原型:

int setjmp(jmp_buf j);
void longjmp(jmp_buf j, int i);


setjmp函数设置返回点,保存调用函数的栈环境与j中(相当于保护现场)。 l

ongjmp的作用是使用setjmp保存在j中的栈环境信息返回到setjmp的位置,也就是当执行longjmp时程序又回到setjmp处(相当于恢复现场)。

setjmp有两个作用:

1)保存调用函数的栈环境于j中,返回值为0

2)作为longjmp的返回目标地,返回值为longjmp的第二个参数i,使代码能够知道它是实际上是通过longjmp返回的

当然,当使用longjmp()时,j的内容被销毁。

3. jmp_buf数据类型

typedef struct __jmp_buf_tag jmp_buf[1];

jmp_buf实际是一个数组,内存分配在栈空间中,作为参数传递时是一个指针(指向调用函数的栈帧)。

4. 具体实例

#include <stdio.h>
#include <setjmp.h>
jmp_buf buf;
void haha()
{
    printf("in haha()\n");
    longjmp(buf,1);
    printf("kaonima\n");
}
int tim=0;
 
int main()
{
    if(setjmp(buf))
    {
        printf("back in main\n");
        tim++;
    }
    else
    {
        printf("first time through\n");
        haha();
    }
    if(tim<3) longjmp(buf,1);
    return 0;
}

运行结果是:

first time through
in haha()
back in main
back in main
back in main

6.异常处理

这里举一个使用这两个函数进行异常处理的例子

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

jmp_buf jb;

void f1()
{
    printf("进入f1()\n");
    if(0/*正确执行*/){ }
    else {
        longjmp(jb,1);
    }
    printf("退出f1()\n");
}
void f2()
{
    printf("进入f2()\n");
    if(1/*正确执行*/) {  }
    else {
        longjmp(jb, 2);
    }
    printf("退出f2()\n");
}

int main()
{
    int r = setjmp(jb);
    if(r==0){
        f1();
        f2();
    }else if(r==1){
        printf("处理错误1\n");
        exit(1);
    }else if(r==2){
        printf("处理错误2\n");
        exit(2);
    }
    return 0;
}

当然完整的异常处理远比这里的代码要复杂,需要考虑异常的嵌套等,这里仅仅给出最简单的思路。

注:不要在C++中使用setjmp和longjmp

C++为异常处理提供了直接支持。除非极特殊需要,不要再重新实现自己的异常机制,尤其需要说明的是,简单的调用setjmp/longjmp有可能带来问题。

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

class MyClass
{
public:
    MyClass(){ printf("MyClass::MyClass()\n");}
    ~MyClass(){ printf("MyClass::~MyClass()\n");}
};
jmp_buf jb;

void f1()
{
    MyClass obj;
    printf("进入f1()\n");
    if(0/*正确执行*/){ }
    else {
        longjmp(jb,1);
    }
    printf("退出f1()\n");
}
void f2()
{
    printf("进入f2()\n");
    if(1/*正确执行*/) {  }
    else {
        longjmp(jb, 2);
    }
    printf("退出f2()\n");
}

int main()
{
    int r = setjmp(jb);
    if(r==0){
        f1();
        f2();
    }else if(r==1){
        printf("处理错误1\n");
        exit(1);
    }else if(r==2){
        printf("处理错误2\n");
        exit(2);
    }
    return 0;
}

g++编译,程序输出:

MyClass::MyClass()
进入f1()
处理错误1

vc++编译,程序输出:

MyClass::MyClass()
进入f1()
MyClass::~MyClass()
处理错误1

longjmp()跳转前局部对象可能并不会析构(g++),也可能析构(VC++),C++标准对此并无明确要求。这种依赖于具体编译器版本的代码是应该避免的。

而C++本身的throw关键字,却能严格保证局部对象构造和析构的成对调用。

参考:

https://blog.csdn.net/smstong/article/details/50728022

https://blog.csdn.net/c1194758555/article/details/52780068

https://blog.csdn.net/qq_33656136/article/details/52732970

C专家编程 6.8节

猜你喜欢

转载自www.cnblogs.com/zzdbullet/p/9932122.html
今日推荐