歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> setjmp與longjmp使用(轉載)

setjmp與longjmp使用(轉載)

日期:2017/3/3 11:26:03   编辑:Linux技術

setjmp與longjmp使用(轉載)

標簽(空格分隔): linux
參考文章:
setjmp和longjmp函數使用詳解

一、setjmp與longjmp用法

非局部跳轉語句—
setjmp
longjmp
函數。
非局部指的是,這不是由普通C語言goto,語句在一個函數內實施的跳轉,而是在棧上跳過若干調用幀,返回到當前函數調用路徑上的某一個函數中。
[code]#include <setjmp.h>
int setjmp(jmp_buf  env);
    // 返回值:若直接調用則返回0,若從longjmp調用返回則返回非0值的longjmp中的val值
void longjmp(jmp_buf env,int val);
    //  調用此函數則返回到語句setjmp所在的地方,其中env 就是setjmp中的 env,而val 則是使setjmp的返回值變為val。

當檢查到一個錯誤時,則以兩個參數調用longjmp函數,第一個就是在調用setjmp時所用的env,第二個參數是具有非0值的val,它將成為從setjmp處返回的值。使用第二個參數的原因是對於一個setjmp可以有多個longjmp。
兩個函數時配合使用的,setjmp先執行,記錄一個棧幀點,當longjmp執行返回一個相應的val值,返回到setjmp的位置
例程如下:
[code]#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和longjmp要注意以下幾點:
setjmp與longjmp結合使用時,它們必須有嚴格的先後執行順序,也即先調用setjmp函數,之後再調用longjmp函數,以恢復到先前被保存的“程序執行點”。否則,如果在setjmp調用之前,執行longjmp函數,將導致程序的執行流變的不可預測,很容易導致程序崩潰而退出
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))) { }在文獻與實踐中是常見的,並保持了相當的可移植性。
[code]#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 */  
}

三、變量的問題

在longjmp返回到setjmp的位置的時候,變量的值處於什麼狀況,會不會回滾數據值?
書上說對此的回答是:“看情況,未知的”
自動變量:不回滾
全局變量:保持不變
寄存器變量:
靜態變量:保持不變
易失變量:不想回滾可以聲明為易失變量。volatile
例程驗證下:
[code]#include <setjmp.h>
#include "apue.h"

static void f1(int, int, int, int);
static void f2(void);

static jmp_buf buf;
static int     globval;

int main(void)
{
    int     autoval;
    register int regival;
    volatile int volaval;
    static int   statval;

    globval = 1; autoval = 2; regival = 3;
    volaval = 4; statval = 5;

    if(setjmp(buf) != 0)
    {
        printf("after longjmp:\n");
        printf("globval = %d, autoval = %d, regival = %d, volaval = %d, statval = %d\n",globval,autoval,regival,volaval,statval);
        exit(0);
    }

    /*change the value after setjmp,but ,before longjmp*/
    globval = 95; autoval = 96; 
    regival = 97; volaval = 98; statval = 99;
    f1(autoval,regival,volaval,statval);     //never return 
    exit(0);
}

static void f1(int i, int j, int k, int l)
{
    printf("in f1():\n");
    printf("globval = %d, autoval = %d, regival = %d, volaval = %d, statval = %d\n",globval, i, j, k, l);
    f2();
}

static void f2(void)
{
    longjmp(buf, 1);
}

程序的輸出:
優化前:
[root@localhost unix_test]# gcc 20160717_longjmp.c //不進行優化
[root@localhost unix_test]# ./a.out
in f1():
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
優化後:
[root@localhost unix_test]# gcc -O 20160717_longjmp.c //全部優化
[root@localhost unix_test]# ./a.out
in f1():
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 2, regival = 3, volaval = 98, statval = 99
結果顯示:
全局變量,靜態變量和易失變量不受優化影響,在longjmp之後,他們的值是最近所呈現的值。
setjmp的手冊中說明如下:存放在存儲器中的變量將具有longjmp時的值,而在cpu和寄存器中的變量則恢復為調用setjmp的值。 不進行優化的時候,所有這5個變量都存放在存儲器中(忽略了這裡對regival的register類型的聲明),而進行優化之後,autoval和regival都存放在寄存器中(即使autoval並未說明為register),volatile變量仍然存儲在存儲器中。
這裡可以明確的是volatile變量是寫非局部跳轉的變量首選。
Copyright © Linux教程網 All Rights Reserved