1. 程式人生 > >setjmp()和longjmp()函式

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節