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

setjmp()和longjmp()函式詳解

今天看《unix環境高階程式設計》發現這兩個函式,還挺有用的,在c異常處理、協程等作用很大。

使用方法

我們知道,想要實現函式內的跳轉可以使用goto語句,但如果需要從一個函式跳轉到另一個函式,goto是不能完成的,那該如何實現呢?

函式間跳轉原理

示例程式碼:

void f()
{
    //...
    Label:
    //...
}

void g()
{
    //...
    GOTO Label;
    //...
}

首先我們要知道,實現這種型別的跳轉,和作業系統中任務切換的上下文切換有點類似,我們只需要恢復 Label 標籤處函式上下文即可。函式的上下文包括以下內容:

  • 函式棧幀,主要是棧幀指標BP和棧頂指標SP
  • 程式指標PC,此處為指向 Label 語句的地址
  • 其它暫存器,這是和體系相關的,在 x86 體系下需要儲存有的 AX/BX/CX 等等 callee-regs。

這樣,在執行 GOTO Label; 這條語句,我們恢復 Label 處的上下文,即完成跳轉到 Label 處的功能。

如果你讀過 Linux 作業系統程序切換的原始碼,你會很明白 Linux 會把程序的上下文儲存在 task_struct 結構體中,切換時直接恢復。這裡我們也可以這樣做,將 Label 處的函式上下文儲存在某個結構體中,但執行到 GOTO Label 語句時,我們從該結構體中恢復函式的上下文。

原型

#include <setjmp.h>
int setjmp(jmp_buf env);

setjmp函式的功能就是將函式在此處的上下文儲存在jmp_buf結構體中,以供longjmp從此結構體中恢復。

  • env 即為儲存函式上下文的jmp_buf結構體變數;
  • 返回值:如果直接呼叫該函式,直接返回0;若該函式從longjmp呼叫返回,返回值為非零值,由longjmp傳進來。根據函式的返回值,我們就可以判斷setjmp是直接第一次呼叫,還是從其他地方跳轉過來的。
void longjmp(jmp_buf env, int val);

longjmp函式的功能是從jmp_buf結構體中恢復由setjmp儲存的上下文,該函式不返回,而是從setjmp函式中返回。

  • 引數 env 是由 setjmp 函式儲存過的上下文;
  • 引數 val 表示從 longjmp 函式傳遞給 setjmp 函式的返回值,如果 val 值為0, setjmp 將會返回1,否則返回 val;
  • longjmp 不直接返回,而是從 setjmp 函式中返回,longjmp 執行完之後,程式就像剛從 setjmp 函式返回一樣。

簡單例項

#include "stdio.h"
#include "setjmp.h"

static jmp_buf buf;

void second(){
    printf("second\n");         // 列印
    longjmp(buf,1); // 跳回setjmp的呼叫處 - 使得setjmp返回值為1
}

void first(){
    second();
    printf("first\n"); // 不可能執行到此行
}

int main(){

    if (!setjmp(buf)){
        first();        // 進入此行前,setjmp返回0
    }else{      // 當longjmp跳轉回,setjmp返回1,因此進入此行
        printf("main\n");  // 列印 
    }
    return 0;
}

輸出結果:
這裡寫圖片描述

C 語言異常處理

Java、C# 等面嚮物件語言中都有異常處理的機制,如下就是典型的 Java 中異常處理的程式碼,兩個數相除,如果被除數為0丟擲異常,在函式 f() 中可以獲取該異常並進行處理:

double divide(double to, double by) throws Bad {
    if(by == 0)
        throw new Bad ("Cannot / 0");
    return to / by;
}

void f() {
    try {
        divide(2, 0);
        //...   
    } catch (Bad e) {
        print(e.getMessage());
    }
    print("done");
}

在 C 語言中雖然沒有類似的異常處理機制,但是我們可以使用 setjmp 和 longjmp 來模擬實現該功能,這也是這兩個函式的一個重要的應用:

static jmp_buf env;

double divide(double to, double by)
{
    if(by == 0)
        longjmp(env, 1);
    return to / by;
}

void f() 
{
    if (setjmp(env) == 0)
        divide(2, 0);
    else
        printf("Cannot / 0");
    printf("done");
}

如果複雜一點,可以根據 longjmp 傳遞的返回值來判斷各種不同的異常,來進行區別的處理,程式碼結構如下:

switch(setjmp(env)):
    case 0:         //default
        //...
    case 1:         //exception 1
        //...
    case 2:         //exception 2
        //...
    //...

關於使用 C 語言來處理異常,可以參見這篇文章,介紹了更多複雜的結構,但無外乎就是 setjmp 和 longjmp 的應用。

下次有時間看看關於這兩個函式在協程中的應用,今天就先到這~

參考:
1、http://www.cnblogs.com/hazir/p/c_setjmp_longjmp.html
2、http://blog.csdn.net/chenyiming_1990/article/details/8683413