Setjmp.h

setjmp.h是C標準函數庫中提供「非本地跳轉」的頭文件：控制流偏離了通常的子程序調用與返回序列。互補的兩個函數setjmp與longjmp提供了這種功能。

setjmp/longjmp的典型用途是異常處理機制的實現：利用longjmp恢復程序或線程的狀態，甚至可以跳過棧中多層的函數調用。

## 成員函數

`int setjmp(jmp_buf&nbsp;env)`｜建立本地的`jmp_buf`緩衝區並且初始化，用於將來跳轉回此處。這個子程序 保存程序的調用環境於`env`參數所指的緩衝區，`env`將被`longjmp`使用。如果是從`setjmp`直接調用返回，`setjmp`返回值為0。如果是從`longjmp`恢復的程序調用環境返回，`setjmp`返回非零值。

`void longjmp(jmp_buf&nbsp;env, int&nbsp;value)`｜恢復`env`所指的緩衝區中的程序調用環境上下文，`env`所指緩衝區的內容是由`setjmp`子程序調用所保存。`value`的值從`longjmp`傳遞給`setjmp`。`longjmp`完成後，程序從對應的`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]`或`struct __jmp_buf_tag`，用於保存恢復調用環境所需的資訊。

## 告誡與限制

`longjmp`實現了非本地跳轉，微軟的IA32程序設計環境中正常的"棧解開"因而沒有發生，所以諸如棧中已定義的局部變量的析構函數的調用（用於銷毀該局部變量）都沒有執行。所有依賴於棧解開調用析構函數所做的掃尾工作，如關閉文件、釋放堆記憶體塊等都沒有做。但在微軟的X64程序設計環境，`longjmp`啟動了正常的"棧解開"。

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

## 使用例子

### 簡單例子

```c
#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`被用於包住一個例外處理，類似`try`。`longjmp`調用類似於`throw`語句，允許一個異常返回給`setjmp`一個異常值。下屬代碼示例遵從1999 ISO C standard與Single UNIX Specification：僅在特定範圍內引用`setjmp`

- `if`，`switch`或它們的嵌套使用的條件表達式
- 上述情況下與`!`一起使用或者與整數常值比較
- 作為單獨的語句(不使用其返回值)

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

```c
#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，使得進程知道該次系統調用失敗。

## 外部連結

- Exceptions in C with Longjmp and Setjmp