非局部跳躍陳述式---setjmp和longjmp函數。非局部指的是,這不是由普通C語言goto,語句在一個函數內實施的跳轉,而是在棧上跳過若干調用幀,返回到當前函數調用路徑上的某一個函數中。
#include <setjmp.h>
Int setjmp(jmp_buf env);
傳回值:若直接調用則返回0,若從longjmp調用返回則返回非0值
Void longjmp(jmp_buf env,int val);
在希望返回到的位置調用setjmp,此位置在main函數中,因為直接調用該函數,所以其傳回值為0.setjmp參數evn的類型是一個特殊的類型jmp_buf,這一資料類型是某種形式的數組,其中存放在調用longjmp時能用來恢複棧狀態的所有資訊。因為需要在另一個函數中引用env變數,所以規範的處理方式是將env變數定義為全域變數。
當檢查到一個錯誤時,則以兩個參數調用longjmp函數,第一個就是在調用setjmp時所用的env,第二個參數是具有非0值的val,它將成為從setjmp處返回的值。使用第二個參數的原因是對於一個setjmp可以有多個longjmp。
下面我們可以看一個簡單的例子:
#include <stdio.h> #include <stdlib.h> #include <setjmp.h> #include <string.h>
void fun1(void); void fun2(void); jmp_buf jmpbuffer; void main(void) { int i = 0; int j = 0; i = setjmp(jmpbuffer); if(i==0) { printf("first run/n"); fun1(); fun2(); } else { switch(i) { case 1: printf("In fun1 /n"); break; case 2: printf("In fun2/n"); break; default: printf("unkown error/n"); break; } exit(0); } return 1; }
void fun1(void) { char *s = "hello"; char *s1 = "Hello"; if(strcmp(s,s1)!=0) longjmp(jmpbuffer,1); }
void fun2(void) { char *s = "world"; if(strcmp(s,"World")!=0) longjmp(jmpbuffer,2); }
|
這個函數的運行結果是:
root@root:~/program/test_program$ ./jmp_test first run In fun1
|
在使用longjmp跳轉到setjmp中時,程式主動的退出了!相當於拋出一個異常退出!其實這兩個函數可以類比C++中的異常函數:
使用setjmp和longjmp要注意以下幾點:
1、setjmp與longjmp結合使用時,它們必須有嚴格的先後執行順序,也即先調用setjmp函數,之後再調用longjmp函數,以恢複到先前被儲存的“程式執行點”。否則,如果在setjmp調用之前,執行longjmp函數,將導致程式的執行流變的不可預測,很容易導致程式崩潰而退出
2、不要假設寄存器類型的變數將總會保持不變。在調用longjmp之後,通過setjmp所返回的控制流程中,程式中寄存器類型的變數將不會被恢複。寄存器類型的變數,是指為了提高程式的運行效率,變數不被儲存在記憶體中,而是直接被儲存在寄存器中。寄存器類型的變數一般都是臨時變數,在C語言中,通過register定義,或直接嵌入彙編代碼的程式。這種類型的變數。
longjmp必須在setjmp調用之後,而且longjmp必須在setjmp的範圍之內。具體來說,在一個函數中使用setjmp來初始化一個全域標號,然後只要該函數未曾返回,那麼在其它任何地方都可以通過longjmp調用來跳轉到 setjmp的下一條語句執行。實際上setjmp函數將發生調用處的局部環境儲存在了一個jmp_buf的結構當中,只要主調函數中對應的記憶體未曾釋放 (函數返回時局部記憶體就失效了),那麼在調用longjmp的時候就可以根據已儲存的jmp_buf參數恢複到setjmp的地方執行。