setjmp.hC標準函式庫中提供「非本地跳轉」的標頭檔控制流偏離了通常的子程式呼叫與返回序列。互補的兩個函數setjmp與longjmp提供了這種功能。

setjmp/longjmp的典型用途是例外處理機制的實現:利用longjmp恢復程式或線程的狀態,甚至可以跳過棧中多層的函數呼叫。

成員函數

int setjmp(jmp_buf env) 建立本地的jmp_buf緩衝區並且初始化,用於將來跳轉回此處。這個子程式[1] 儲存程式的呼叫環境於env參數所指的緩衝區,env將被longjmp使用。如果是從setjmp直接呼叫返回,setjmp返回值為0。如果是從longjmp恢復的程式呼叫環境返回,setjmp返回非零值。
void longjmp(jmp_buf env, int value) 恢復env所指的緩衝區中的程式呼叫環境上下文,env所指緩衝區的內容是由setjmp子程式[1]呼叫所儲存。value的值從longjmp傳遞給setjmplongjmp完成後,程式從對應的setjmp呼叫處繼續執行,如同setjmp呼叫剛剛完成。如果value傳遞給longjmp零值,setjmp的返回值為1;否則,setjmp的返回值為value

setjmp儲存當前的環境(即程式的狀態)到平台相關的一個數據結構 (jmp_buf),該數據結構在隨後程式執行的某一點可被 longjmp用於恢復程式的狀態到setjmp呼叫所儲存到jmp_buf時的原樣。這一過程可以認為是"跳轉"回setjmp所儲存的程式執行狀態。setjmp的返回值指出控制是正常到達該點還是通過呼叫longjmp恢復到該點。因此有編程的慣用法: if( setjmp(x) ){/* handle longjmp(x) */}

成員類型

jmp_buf 陣列類型,例如struct int[16][2]struct __jmp_buf_tag[3],用於儲存恢復呼叫環境所需的資訊。

告誡與限制

longjmp實現了非本地跳轉,微軟的IA32程式設計環境中正常的"棧卷回"("stack unwinding")因而沒有發生,所以諸如棧中已定義的局部變數的解構函式的呼叫(用於銷毀該局部變數)都沒有執行。所有依賴於棧卷回呼用解構函式所做的掃尾工作,如關閉檔案、釋放堆主記憶體塊等都沒有做。但在微軟的X64程式設計環境,longjmp啟動了正常的"棧卷回"。[4]

如果setjmp所在的函數已經呼叫返回了,那麼longjmp使用該處setjmp所填寫的對應jmp_buf緩衝區將不再有效。這是因為longjmp所要返回的"堆疊幀"(stack frame)已經不再存在了,程式返回到一個不再存在的執行點,很可能覆蓋或者弄壞程式棧.[5][6]

使用例子

簡單例子

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

static jmp_buf buf;

void second(void) {
    printf("second\n");         // 打印
    longjmp(buf,1);             // 跳回setjmp的调用处 - 使得setjmp返回值为1
}

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

int main() {   
    if ( ! setjmp(buf) ) {
        first();                // 进入此行前,setjmp返回0
    } else {                    // 当longjmp跳转回,setjmp返回1,因此进入此行
        printf("main\n");       // 打印
    }

    return 0;
}

上述程式將輸出:

second
main

注意到雖然first()子程式被呼叫,"first"不可能被列印。"main"被列印,因為條件陳述式if ( ! setjmp(buf) )被執行第二次。

例外處理

在下例中,setjmp被用於包住一個例外處理,類似trylongjmp呼叫類似於throw陳述式,允許一個異常返回給setjmp一個異常值。下屬代碼範例遵從1999 ISO C standardSingle UNIX Specification:僅在特定範圍內參照setjmp

  • ifswitch或它們的巢狀使用的條件表達式
  • 上述情況下與!一起使用或者與整數常值比較
  • 作為單獨的陳述式(不使用其返回值)

遵從上述規則使得建立程式環境緩衝區更為容易。更一般的使用setjmp可能引起未定義行為,如破壞局部變數;編譯器被要求保護或警告這些用法。但輕微的複雜用法如switch ((exception_type = setjmp(env))) { }在文獻與實踐中是常見的,並保持了相當的可移植性。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
 
void first(void);
void second(void);
 
/* This program's output is:
 
calling first
calling second
entering second
second failed with type 3 exception; remapping to type 1.
first failed, exception type 1
 
*/
 
/* Use a file scoped static variable for the exception stack so we can access
 * it anywhere within this translation unit. */
static jmp_buf exception_env;
static int exception_type;
 
int main() {
    void *volatile mem_buffer;
 
    mem_buffer = NULL;
    if (setjmp(exception_env)) {
        /* if we get here there was an exception */
        printf("first failed, exception type %d\n", exception_type);
    } else {
        /* Run code that may signal failure via longjmp. */
        printf("calling first\n");
        first();
        mem_buffer = malloc(300); /* allocate a resource */
        printf("%s",strcpy((char*) mem_buffer, "first succeeded!")); /* ... this will not happen */
    }
    if (mem_buffer)
        free((void*) mem_buffer); /* carefully deallocate resource */
    return 0;
}
 
void first(void) {
    jmp_buf my_env;
 
    printf("calling second\n");
    memcpy(my_env, exception_env, sizeof(jmp_buf));
    switch (setjmp(exception_env)) {
        case 3:
            /* if we get here there was an exception. */
            printf("second failed with type 3 exception; remapping to type 1.\n");
            exception_type = 1;

        default: /* fall through */
            memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
            longjmp(exception_env, exception_type); /* continue handling the exception */

        case 0:
            /* normal, desired operation */
            second();
            printf("second succeeded\n");  /* not reached */
    }
    memcpy(exception_env, my_env, sizeof(jmp_buf)); /* restore exception stack */
}
 
void second(void) {
    printf("entering second\n" ); /* reached */
    exception_type = 3;
    longjmp(exception_env, exception_type); /* declare that the program has failed */
    printf("leaving second\n"); /* not reached */
}

用於訊號處理

訊號處理機制中,行程在檢查收到的訊號,會從原來的系統呼叫中直接返回,而不是等到該呼叫完成。這種行程突然改變其上下文的情況,就是通過使用setjmp和longjmp來實現的。setjmp將儲存的上下文載入用戶空間,並繼續在舊的上下文中繼續執行。這就是說,行程執行一個系統呼叫,當因為資源或其他原因要去睡眠時,內核為行程作了一次setjmp,如果在睡眠中被訊號喚醒,行程不能再進入睡眠時,內核為行程呼叫longjmp,該操作是內核為行程將現在的上下文交換成原先通過setjmp呼叫儲存在行程用戶區的上下文,這樣就使得行程可以恢復等待資源前的狀態,而且內核為setjmp返回1,使得行程知道該次系統呼叫失敗。

參考文獻

外部連結

Wikiwand in your browser!

Seamless Wikipedia browsing. On steroids.

Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.

Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.