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變量是寫非局部跳轉的變量首選。