與刺激的abort()和exit()相比,goto語句看起來是處理異常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函數內部的標號上,而不能將控制權轉移到所在程序的任意地點(當然,除非你的所有代碼都在main體中)。
為了解決這個限制,C函數庫提供了setjmp()和longjmp()函數,它們分別承擔非局部標號和goto作用。頭文件<setjmp.h>申明了這些函數及同時所需的jmp_buf數據類型。
setjmp.h是C標准函數庫中提供“非本地跳轉”的頭文件:控制流偏離了通常的子程序調用與返回串行。互補的兩個函數setjmp與longjmp提供了這種功能。
setjmp/longjmp的典型用途是異常處理機制的實現:利用longjmp恢復程序或線程的狀態,甚至可以跳過棧中多層的函數調用。
setjmp保存當前的環境(即程序的狀態)到平台相關的一個數據結構 (jmp_buf),該數據結構在隨後程序執行的某一點可被 longjmp用於恢復程序的狀態到setjmp調用所保存到jmp_buf時的原樣。這一過程可以認為是"跳轉"回setjmp所保存的程序執行狀態。setjmp的返回值指出控制是正常到達該點還是通過調用longjmp恢復到該點。
原理非常簡單:
1.setjmp(j)設置“jump”點,用正確的程序上下文填充jmp_buf對象j。這個上下文包括程序存放位置、棧和框架指針,其它重要的寄存器和內存數據。當初始化完jump的上下文,setjmp()返回0值。
2. 以後調用longjmp(j,r)的效果就是一個非局部的goto或“長跳轉”到由j描述的上下文處(也就是到那原來設置j的setjmp()處)。當作為長跳轉的目標而被調用時,setjmp()返回r或1(如果r設為0的話)。(記住,setjmp()不能在這種情況時返回0。)
jmp_buf 數組類型,例如struct int[16]或struct __jmp_buf_tag,用於保存恢復調用環境所需的信息
通過有兩類返回值,setjmp()讓你知道它正在被怎麼使用。當設置j時,setjmp()如你期望地執行;但當作為長跳轉的目標時,setjmp()就從外面“喚醒”它的上下文。你可以用longjmp()來終止異常,用setjmp()標記相應的異常處理程序。
#include <setjmp.h>
#include <stdio.h>
jmp_buf j;
void raise_exception(void)
{
printf("exception raised\n");
longjmp(j, 1); /* jump to exception handler */
printf("this line should never appear\n");
}
int main(void)
{
if(setjmp(j) == 0)
{
printf("\''setjmp\'' is initializing \''j\''\n");
raise_exception();
printf("this line should never appear\n");
}
else
{
printf("''setjmp'' was just jumped into\n");
/* this code is the exception handler */
}
return 0;
}
/* When run yields:
''setjmp'' is initializing ''j''
exception raised
''setjmp'' was just jumped into
*/
那個填充jmp_buf的函數不在調用longjmp()之前返回。否則,存儲在jmp_buf中的上下文就有問題了:
jmp_buf j;
void f(void)
{
setjmp(j);
}
int main(void)
{
f();
longjmp(j, 1); /* logic error */
return 0;
}
所以,你必須把setjmp()處理成只是到其所在位置的一個非局部跳轉。
Longjmp()和setjmp()聯合體運行於異常生命期的2和3階段。longjmp(j,r)產生異常對象r(一個整數),並且作為返回值傳送到setjmp(j)處。實際上,setjmp()函數通報了異常r。
下面這個例子采用switch,能更好的展現這對函數的功能:
#include <setjmp.h>
#include <stdio.h>
jmp_buf j;
void raise_exception(void)
{
printf("exception raised\n");
longjmp(j, 3); /* jump to exception handler case 3 */
printf("this line should never appear\n");
}
int main(void)
{
switch (setjmp(j))
{
case 0:
printf("''setjmp'' is initializing ''j''\n");
raise_exception();
printf("this line should never appear\n");
case 1:
printf("Case 1\n");break;
case 2:
printf("Case 2\n");break;
case 3:
printf("Case 3\n");break;
default:
break;
}
return 0;
}