在C中,goto語句是不能跨越函數的,而執行這樣跳轉功能的是函數setjmp和longjmp。這兩個函數對于處理發生在深層嵌套函數調用中的出錯情況是非常有用的。
setjmp和longjmp函數也稱為非局部goto,非局部指的是,這不是由普通C語言goto語句在一個函數內實施的跳轉,而是在棧上跳過若干調用幀,返回到當前函數調用路徑上的某一函數中。
#include <setjmp.h>int setjmp( jmp_buf env );返回值:若直接調用則返回0,若從longjmp調用返回則返回非0值void longjmp( jmp_buf env, int val );
圖7-4 調用cmd_add后的各個棧幀(函數調用關系為:main先調用do_line,do_line又調用cmd_add)
在希望返回到的位置調用setjmp,假定此位置在main函數中,因為我們是直接調用該函數,所以其返回值為0。setjmp參數env的類型是一個特殊類型jmp_buf。這一數據類型是某種形式的數組,其中存放:在調用longjmp時能用來恢復棧狀態的所有信息。因為需要在另一個函數中引用env變量,所以規范的處理方式是將env變量定義為全局變量。
當檢查到一個錯誤時,例如在cmd_add函數中,則以兩個參數調用longjmp函數。第一個就是在調用setjmp時所用的env;第二個參數是具有非0值的val,它將成為從setjmp處返回的值。使用第二個參數的原因是對于一個setjmp可以有多個longjmp。例如,可以在cmd_add中以val為1調用longjmp,也可在do_line中以val為2調用longjmp。在setjmp的返回值就會是1或2,通過測試返回值就可以判斷造成返回的longjmp是在cmd_add還是在do_line中。
我們假定在main中調用了setjmp(jmpbuffer),在cmd_add中調用了longjmp(jmpbuffer, 1)為例進行后續說明。
...#include <setjmp.h>...jmp_buf jmpbuffer;intmain(void){ ... if( setjmp(jmpbuffer) != 0 ) PRintf("error"); ...}...void cmd_add(void){ ... if( ... ) /* an error has occurred */ longjmp( jmpbuffer, 1 ); ...}
執行main時,調用setjmp,它將所需的信息記入變量jmpbuffer中并返回0。然后調用do_line,它又調用cmd_add,假定在其中檢查到一個錯誤。在cmd_add中調用longjmp之前,棧的形式如圖7-4所示。但是longjmp使棧反繞(rewind to)到執行main函數時的情況,也就是拋棄了cmd_add和do_line的棧幀(見圖7-5)。調用longjmp造成main中setjmp的返回,但是,這一次的返回值是1(longjmp的第二個參數)。
圖7-5 調用longjmp后的棧幀(f1代表do_line、f2代表cmd_add)
1、自動、寄存器和易失變量
調用longjmp時,大多數實現并不回滾自動變量和寄存器變量的值,而所有標準則說它們的值是不確定的。如果你有一個自動變量,而又不想使其值回滾,則可定義其為具有volatile的屬性。聲明為全局或靜態變量的值在執行longjmp時保持不變。
程序清單7-6 longjmp對各類變量的影響
[root@localhost apue]# cat prog7-6.c#include "apue.h"#include <setjmp.h>static void f1(int, int, int, int);static void f2(void);static jmp_buf jmpbuffer;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(jmpbuffer) != 0) { printf("after longjmp:/n"); printf("globval = %d, autoval = %d, regival = %d," "volaval = %d, statval = %d/n", /* ISO C的字符串連接功能 */ globval, autoval, regival, volaval, statval); exit(0); } /* * Change variables after setjmp, but before longjmp. */ globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99; f1(autoval, regival, volaval, statval); /* never returns */ exit(0);}static voidf1(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 voidf2(void){ longjmp(jmpbuffer, 1);}
如果以不帶優化和帶優化選項對此程序分別進行編譯,然后運行它們,則得到的結果是不同的:
[root@localhost apue]# cc -o prog7-6 prog7-6.c 不進行任何優化的編譯[root@localhost apue]# ./prog7-6in f1():globval = 95, autoval = 96, regival = 97,volaval = 98, statval = 99after longjmp:globval = 95, autoval = 96, regival = 97,volaval = 98, statval = 99[root@localhost apue]# cc -o prog7-6 -O prog7-6.c 進行全部優化的編譯[root@localhost apue]# ./prog7-6in f1():globval = 95, autoval = 96, regival = 97,volaval = 98, statval = 99after longjmp:globval = 95, autoval = 2, regival = 3,volaval = 98, statval = 99
注意,全局、靜態和易失變量不受優化的影響,在調用longjmp后,它們的值是最近所呈現的值。
某個系統的setjmp(3)手冊頁上說明,存放在存儲器中的變量將具有longjmp時的值,而在CPU和浮點寄存器中的變量則恢復為調用setjmp時的值。
不進行優化時,所有這5個變量都存放在存儲器中(亦即忽略了對regival變量的register存儲類說明)。而進行了優化后,autoval和regival都存放在寄存器中(即使autoval并未聲明為register),volatile變量則仍存放在存儲器中。
通過這一實例我們可以理解到,如果要編寫一個使用非局部跳轉的可移植程序,則必須使用volatile屬性。
2、自動變量的潛在問題
基本規則是聲明自動變量的函數已經返回后,不能再引用這些自動變量。
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答