setjmp.h

C標準函式庫 |
---|
一般 |
雜項 |
setjmp.h是C標準函數庫中提供「非本地跳轉」的頭文件:控制流偏離了通常的子程序調用與返回序列。互補的兩個函數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 傳遞給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] [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
被用於包住一個例外處理,類似try
。longjmp
調用類似於throw
語句,允許一個異常返回給setjmp
一個異常值。下屬代碼示例遵從1999 ISO C standard與Single UNIX Specification:僅在特定範圍內引用setjmp
if
,switch
或它們的嵌套使用的條件表達式- 上述情況下與
!
一起使用或者與整數常值比較 - 作為單獨的語句(不使用其返回值)
遵從上述規則使得創建程序環境緩衝區更為容易。更一般的使用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,使得進程知道該次系統調用失敗。
參考文獻
[編輯]- ^ 1.0 1.1 ISO C標準要求
setjmp
必須是宏實現,但POSIX明確稱未定義setjmp
是宏實現還是函數實現。 - ^ Visual Studio 2008用法
- ^ GNU C 函式庫 2.7的用法
- ^ Microsoft Visual C++ 2010 x32或x64與Intel ICC 2011 (version 12) x32或x64,編譯結果都是
longjmp
啟動了正常的"棧卷回"。但GCC 4.4 x32版編譯的longjmp
不執行"棧卷回"。可見,是否「棧卷回」不具有移植性。 - ^ CS360 Lecture Notes — Setjmp and Longjmp. [2011-06-06]. (原始內容存檔於2010-07-04).
- ^ setjmp(3). [2011-06-06]. (原始內容存檔於2009-07-26).