linux——setjmp()和longjmp()函式的使用
setjmp()和longjmp()函式
與刺激的abort()和exit()相比,goto語句看起來比較適合處理異常的情況。不過,goto是本地的,只能跳到所在函式內部的標號上,而不能將控制權轉移到所在程式的任意地點。
為了解決這個限制,C函式庫提供了setjmp()和longjmp()函式。setjmp()進行非區域性標號的設定,而longjmp()是實現跳轉的功能,調轉到設定的標號處setjmp()。
標頭檔案**<setjmp.h>**申明瞭這些函式及同時所需的jmp_buf資料型別。
原理非常簡單:
1.setjmp(j)設定“jump”點,用正確的程式上下文填充jmp_buf物件j。這個上下文包括程式存放位置、棧和框架指標,其它重要的暫存器和記憶體資料。當初始化完jump的上下文,setjmp()返回0值。也就是第一次呼叫setjmp(j),初始化成功了,返回0。
2. 以後呼叫longjmp(j,r)的效果就是一個非區域性的goto或“長跳轉”到由j描述的上下文處(也就是到那原來設定j的setjmp()處)。在作為長跳轉的目標而被呼叫時(不是第一次初始化的時候),若longjmp(j,r)函式中r是大於0的整數,則跳轉到setjmp(j)會返回r;若longjmp(j,r)中r是0,即longjmp(j,0),那麼跳轉到setjmp(j)後,setjmp(j)返回時1,不會返回0。記住,setjmp()是不會在longjmp(j,0)時返回0的。
通過有兩類返回值,setjmp()讓你知道它正在被怎麼使用。當第一次設定j時,setjmp(j)如你期望地執行;但當作為長跳轉的目標時,setjmp()就從外面“喚醒”它的上下文。
實際程式碼測試
(1)測試1
#include <setjmp.h>
#include <stdio.h>
jmp_buf j;
void test1(void)
{
longjmp(j, 1); /* jump to setjmp then do case 1 */
printf("this line should never appear\n");
}
int main(void)
{
switch (setjmp(j))
{
case 0:
printf("setjmp(j)==0 mains: it is initialized\n");
test1();
printf("this line should never appear\n");
break;
case 1:
printf("Case 1\n");
break;
default:
break;
}
return 0;
}
gcc jmp1.c 編譯, ./a.out執行後,結果如下:
setjmp(j)==0 mains: it is initialized
Case 1
main函式中,第一次執行setjmp(j),setjmp(j)返回0,進入case 0,列印setjmp(j)==0 mains: it is initialized資訊;
之後執行test1()函式,在test()函式中執行 longjmp(j, 1);直接跳轉到main中的setjmp(j),main中和test1()中的printf(“this line should never appear\n”);語句是永遠也不會執行的。
跳轉到main中的setjmp(j)後,setjmp(j)執行返回1,進行case 1 分支,列印Case 1,break跳出,程式結束。
(2)測試2
#include <setjmp.h>
#include <stdio.h>
jmp_buf j;
void test1(void)
{
longjmp(j, 0);
printf("this line should never appear\n");
}
int main(void)
{
switch (setjmp(j))
{
case 0:
printf("setjmp(j)==0 mains: it is initialized\n");
test1();
printf("this line should never appear\n");
break;
case 1:
printf("Case 1\n");
break;
case 2:
printf("Case 2\n");
break;
default:
break;
}
return 0;
}
將test1()中longjmp(j, 1);更改為longjmp(j, 0),編譯執行,結果如下:
setjmp(j)==0 mains: it is initialized
Case 1
第一次執行setjmp(j),進行初始化,返回0;
之後執行longjmp(j, 0);跳轉到setjmp(j)返回1,不會返回0。
即上面強調過的:
在作為長跳轉的目標而被呼叫時(不是第一次初始化的時候),若longjmp(j,r)函式中r是大於0的整數,則跳轉到setjmp(j)會返回r;若longjmp(j,r)中r是0,即longjmp(j,0),那麼跳轉到setjmp(j)後,setjmp(j)返回時1,不會返回0。setjmp()是不會在longjmp(j,0)時返回0的。
(3)
#include <setjmp.h>
#include <stdio.h>
#include <signal.h>
jmp_buf j;
void signal_hander(int signer)
{
longjmp(j, 3); /*get intterrupt sign , jump to setjmp ,do case 3, jump out */
}
void test1(void)
{
longjmp(j, 2); /* jump to setjmp then do case 2 */
printf("this line should never appear\n");
}
void test2(void)
{
longjmp(j, 1); /* jump to setjmp then do case 1 */
printf("this line should never appear\n");
}
int main(void)
{
signal(SIGINT, &signal_hander);
switch (setjmp(j))
{
case 0:
printf("setjmp(j)==0 mains: it is initialized\n");
test1();
printf("this line should never appear\n");
break;
case 1:
printf("jump to Case 2\n");
test1();
break;
case 2:
printf("jump to Case 1\n");
test2();
break;
case 3:
printf("interrupted then jump out\n");
break;
default:
break;
}
return 0;
}
編譯,執行,結果如下:
如上,main函式中第一次執行setjmp(j)進行初始化後,進入case 0 分支,執行test1();
test1()中執行longjmp(j,2),除錯到轉case 2分支,執行test2();
test2()中執行longjmp(j,1),除錯到轉case 1分支,執行test1();
所以程式正常執行時在case 1 和case 2 分支之間無限迴圈。
main函式中設定signal(SIGINT, &signal_hander);可以捕獲中斷訊號;當按下ctrl +C ,捕獲到中斷訊號,執行 signal_hander()函式,呼叫longjmp(j,3)後跳轉到case 3 ,列印interrupted then jump out後程序break跳出結束。
總結:
setjmp(j)設定“jump”點,用正確的程式上下文填充jmp_buf物件j。這個上下文包括程式存放位置、棧和框架指標,其它重要的暫存器和記憶體資料。當初始化完jump的上下文,setjmp()返回0值。也就是第一次呼叫setjmp(j),初始化成功了,返回0。
以後呼叫longjmp(j,r)的效果就是一個非區域性的goto或“長跳轉”到由j描述的上下文處(也就是到那原來設定j的setjmp()處)。在作為長跳轉的目標而被呼叫時(不是第一次初始化的時候),若longjmp(j,r)函式中r是大於0的整數,則跳轉到setjmp(j)會返回r;若longjmp(j,r)中r是0,即longjmp(j,0),那麼跳轉到setjmp(j)後,setjmp(j)返回時1,不會返回0。setjmp()是不會在longjmp(j,0)時返回0的。