在網上看到的,覺得很有用,copy過來的。
非區域性跳轉語句—setjmp和longjmp函式。非區域性指的是,這不是由普通C語言goto,語句在一個函式內實施的跳轉,而是在棧上跳過若干呼叫幀,返回到當前函式呼叫路徑上的某一個函式中。
1 2 |
#include <setjmp.h> Int setjmp(jmp_buf env); |
返回值:若直接呼叫則返回0,若從longjmp呼叫返回則返回非0值的longjmp中的val值
1 |
void longjmp(jmp_buf env,int val); |
呼叫此函式則返回到語句setjmp所在的地方,其中env 就是setjmp中的 env,而val 則是使setjmp的返回值變為val。
當檢查到一個錯誤時,則以兩個引數呼叫longjmp函式,第一個就是在呼叫setjmp時所用的env,第二個引數是具有非0值的val,它將成為從setjmp處返回的值。使用第二個引數的原因是對於一個setjmp可以有多個longjmp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#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; } |
上述程式將輸出:
1 2 |
second main |
注意到雖然first()
子程式被呼叫,”first
“不可能被列印。”main
“被列印,因為條件語句if ( ! setjmp(buf) )
被執行第二次。
使用setjmp和longjmp要注意以下幾點:
1、setjmp與longjmp結合使用時,它們必須有嚴格的先後執行順序,也即先呼叫setjmp函式,之後再呼叫longjmp函式,以恢復到先前被儲存的“程式執行點”。否則,如果在setjmp呼叫之前,執行longjmp函式,將導致程式的執行流變的不可預測,很容易導致程式崩潰而退出
2. longjmp必須在setjmp呼叫之後,而且longjmp必須在setjmp的作用域之內。具體來說,在一個函式中使用setjmp來初始化一個全域性標號,然後只要該函式未曾返回,那麼在其它任何地方都可以通過longjmp呼叫來跳轉到 setjmp的下一條語句執行。實際上setjmp函式將發生呼叫處的區域性環境儲存在了一個jmp_buf的結構當中,只要主調函式中對應的記憶體未曾釋放 (函式返回時區域性記憶體就失效了),那麼在呼叫longjmp的時候就可以根據已儲存的jmp_buf引數恢復到setjmp的地方執行。
異常處理
在下例中,setjmp
被用於包住一個例外處理,類似try
。longjmp
呼叫類似於throw
語句,允許一個異常返回給setjmp
一個異常值。下屬程式碼示例遵從1999 ISO C standard與Single UNIX Specification:僅在特定範圍內引用setjmp
if
,switch
或它們的巢狀使用的條件表示式- 上述情況下與
!
一起使用或者與整數常值比較 - 作為單獨的語句(不使用其返回值)
遵從上述規則使得建立程式環境緩衝區更為容易。更一般的使用setjmp
可能引起未定義行為,如破壞區域性變數;編譯器被要求保護或警告這些用法。但輕微的複雜用法如switch ((exception_type = setjmp(env))) { }
在文獻與實踐中是常見的,並保持了相當的可移植性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
#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(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 */ } |