• 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()
    ");
        longjmp(buf,1);
        printf("kaonima
    ");
    }
    int tim=0;
     
    int main()
    {
        if(setjmp(buf))
        {
            printf("back in main
    ");
            tim++;
        }
        else
        {
            printf("first time through
    ");
            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()
    ");
        if(0/*正确执行*/){ }
        else {
            longjmp(jb,1);
        }
        printf("退出f1()
    ");
    }
    void f2()
    {
        printf("进入f2()
    ");
        if(1/*正确执行*/) {  }
        else {
            longjmp(jb, 2);
        }
        printf("退出f2()
    ");
    }
    
    int main()
    {
        int r = setjmp(jb);
        if(r==0){
            f1();
            f2();
        }else if(r==1){
            printf("处理错误1
    ");
            exit(1);
        }else if(r==2){
            printf("处理错误2
    ");
            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()
    ");}
        ~MyClass(){ printf("MyClass::~MyClass()
    ");}
    };
    jmp_buf jb;
    
    void f1()
    {
        MyClass obj;
        printf("进入f1()
    ");
        if(0/*正确执行*/){ }
        else {
            longjmp(jb,1);
        }
        printf("退出f1()
    ");
    }
    void f2()
    {
        printf("进入f2()
    ");
        if(1/*正确执行*/) {  }
        else {
            longjmp(jb, 2);
        }
        printf("退出f2()
    ");
    }
    
    int main()
    {
        int r = setjmp(jb);
        if(r==0){
            f1();
            f2();
        }else if(r==1){
            printf("处理错误1
    ");
            exit(1);
        }else if(r==2){
            printf("处理错误2
    ");
            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节

  • 相关阅读:
    学习笔记 js跨域请求
    使用Crate dunce获得绝对路径
    自己实现的组件库
    320 什么是 WXML 模版
    325 笛卡尔积算法
    324 借用构造函数继承
    322 n个台阶 一次只能走 一步或者两步,问有多少种走法
    322 原型链继承
    320 说说 SSR?
    321 实现1物理像素边框
  • 原文地址:https://www.cnblogs.com/zzdbullet/p/9932122.html
Copyright © 2020-2023  润新知