1. 程式人生 > >linux——setjmp()和longjmp()函式的使用

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的。