http://home.lupaworld.com/index.php/home/home-space-uid-32446-do-blog-id-131450.html
php
爲了更好地、更方便地支持異常處理編程機制,使得程序員在C語言開發的程序中,能寫出更高效、更友善的帶有異常處理機制的代碼模塊來。因而,C語言中出現了一種更優雅的異常處理機制,那就是setjmp()函數與longjmp()函數。html
實際上,這種異常處理的機制不是C語言中自身的一部分,而是在C標準庫中實現的兩個很是有技巧的庫函數,也許大多數C程序員朋友們對它都很熟悉,並且,經過使用setjmp()函數與 longjmp()函數組合後,而提供的對程序的異常處理機制,以被普遍運用到許多C語言開發的庫系統中,如jpg解析庫,加密解密庫等等。程序員
也許C語言中的這種異常處理機制,較goto語句相比較,它纔是真正意義上的、概念上比較完全的,一種異常處理機制。
setjmp函數有何做用?redis
前面剛說了,setjmp是C標準庫中提供的一個函數,它的做用是保存程序當前運行的一些狀態。它的函數原型以下:
int setjmp( jmp_buf env );編程
這是MSDN中對它的評論,以下:數組
setjmp函數用於保存程序的運行時的堆棧環境,接下來的其它地方,你能夠經過調用longjmp函數來恢復先前被保存的程序堆棧環境。當 setjmp和longjmp組合一塊兒使用時,它們能提供一種在程序中實現「非本地局部跳轉」("non-local goto")的機制。而且這種機制經常被用於來實現,把程序的控制流傳遞到錯誤處理模塊之中;或者程序中不採用正常的返回(return)語句,或函數的正常調用等方法,而使程序能被恢復到先前的一個調用例程(也即函數)中。安全
對setjmp函數的調用時,會保存程序當前的堆棧環境到env參數中;接下來調用longjmp時,會根據這個曾經保存的變量來恢復先前的環境,而且當前的程序控制流,會所以而返回到先前調用setjmp時的程序執行點。此時,在接下來的控制流的例程中,所能訪問的全部的變量(除寄存器類型的變量之外),包含了longjmp函數調用時,所擁有的變量。數據結構
setjmp和longjmp並不能很好地支持C++中面向對象的語義。所以在C++程序中,請使用C++提供的異常處理機制。多線程
好了,如今已經對setjmp有了很感性的瞭解,暫且不作過多評論,接着往下看longjmp函數。app
longjmp函數有何做用?
一樣,longjmp也是C標準庫中提供的一個函數,它的做用是用於恢復程序執行的堆棧環境,它的函數原型以下:
void longjmp( jmp_buf env, int value );
這是MSDN中對它的評論,以下:
longjmp函數用於恢復先前程序中調用的setjmp函數時所保存的堆棧環境。setjmp和longjmp組合一塊兒使用時,它們能提供一種在程序中實現「非本地局部跳轉」("non-local goto")的機制。而且這種機制經常被用於來實現,把程序的控制流傳遞到錯誤處理模塊,或者不採用正常的返回(return)語句,或函數的正常調用等方法,使程序能被恢復到先前的一個調用例程(也即函數)中。
對setjmp函數的調用時,會保存程序當前的堆棧環境到env參數中;接下來調用longjmp時,會根據這個曾經保存的變量來恢復先前的環境,而且所以當前的程序控制流,會返回到先前調用setjmp時的執行點。此時,value參數值會被setjmp函數所返回,程序繼續得以執行。而且,在接下來的控制流的例程中,它所可以訪問到的全部的變量(除寄存器類型的變量之外),包含了longjmp函數調用時,所擁有的變量;而寄存器類型的變量將不可預料。setjmp函數返回的值必須是非零值,若是longjmp傳送的value參數值爲0,那麼實際上被setjmp返回的值是1。
在調用setjmp的函數返回以前,調用longjmp,不然結果不可預料。
在使用longjmp時,請遵照如下規則或限制:
· 不要假象寄存器類型的變量將總會保持不變。在調用longjmp以後,經過setjmp所返回的控制流中,例程中寄存器類型的變量將不會被恢復。
· 不要使用longjmp函數,來實現把控制流,從一箇中斷處理例程中傳出,除非被捕獲的異常是一個浮點數異常。在後一種狀況下,若是程序經過調用_fpreset函數,來首先初始化浮點數包後,它是能夠經過longjmp來實現從中斷處理例程中返回。
· 在C++程序中,當心對setjmp和longjmp的使用,應爲setjmp和longjmp並不能很好地支持C++中面向對象的語義。所以在C++程序中,使用C++提供的異常處理機制將會更加安全。
把setjmp和longjmp組合起來,原來它這麼厲害!
如今已經對setjmp和longjmp都有了很感性的瞭解,接下來,看一個示例,並從這個示例展開分析,示例代碼以下(來源於MSDN):
/* FPRESET.C: This program uses signal to set up a
* routine for handling floating-point errors.
*/
#i nclude <stdio.h>
#i nclude <signal.h>
#i nclude <setjmp.h>
#i nclude <stdlib.h>
#i nclude <float.h>
#i nclude <math.h>
#i nclude <string.h>
jmp_buf mark; /* Address for long jump to jump to */
int fperr; /* Global error number */
void __cdecl fphandler( int sig, int num ); /* Prototypes */
void fpcheck( void );
void main( void )
{
double n1, n2, r;
int jmpret;
/* Unmask all floating-point exceptions. */
_control87( 0, _MCW_EM );
/* Set up floating-point error handler. The compiler
* will generate a warning because it expects
* signal-handling functions to take only one argument.
*/
if( signal( SIGFPE, fphandler ) == SIG_ERR )
{
fprintf( stderr, "Couldn"t set SIGFPEn" );
abort(); }
/* Save stack environment for return in case of error. First
* time through, jmpret is 0, so true conditional is executed.
* If an error occurs, jmpret will be set to -1 and false
* conditional will be executed.
*/
// 注意,下面這條語句的做用是,保存程序當前運行的狀態
jmpret = setjmp( mark );
if( jmpret == 0 )
{
printf( "Test for invalid operation - " );
printf( "enter two numbers: " );
scanf( "%lf %lf", &n1, &n2 );
// 注意,下面這條語句可能出現異常,
// 若是從終端輸入的第2個變量是0值的話
r = n1 / n2;
/* This won"t be reached if error occurs. */
printf( "nn%4.3g / %4.3g = %4.3gn", n1, n2, r );
r = n1 * n2;
/* This won"t be reached if error occurs. */
printf( "nn%4.3g * %4.3g = %4.3gn", n1, n2, r );
}
else
fpcheck();
}
/* fphandler handles SIGFPE (floating-point error) interrupt. Note
* that this prototype accepts two arguments and that the
* prototype for signal in the run-time library expects a signal
* handler to have only one argument.
*
* The second argument in this signal handler allows processing of
* _FPE_INVALID, _FPE_OVERFLOW, _FPE_UNDERFLOW, and
* _FPE_ZERODIVIDE, all of which are Microsoft-specific symbols
* that augment the information provided by SIGFPE. The compiler
* will generate a warning, which is harmless and expected.
*/
void fphandler( int sig, int num )
{
/* Set global for outside check since we don"t want
* to do I/O in the handler.
*/
fperr = num;
/* Initialize floating-point package. */
_fpreset();
/* Restore calling environment and jump back to setjmp. Return
* -1 so that setjmp will return false for conditional test.
*/
// 注意,下面這條語句的做用是,恢復先前setjmp所保存的程序狀態
longjmp( mark, -1 );
}
void fpcheck( void )
{
char fpstr[30];
switch( fperr )
{
case _FPE_INVALID:
strcpy( fpstr, "Invalid number" );
break;
case _FPE_OVERFLOW:
strcpy( fpstr, "Overflow" );
break;
case _FPE_UNDERFLOW:
strcpy( fpstr, "Underflow" );
break;
case _FPE_ZERODIVIDE:
strcpy( fpstr, "Divide by zero" );
break;
default:
strcpy( fpstr, "Other floating point error" );
break;
}
printf( "Error %d: %sn", fperr, fpstr );
}
程序的運行結果以下:
Test for invalid operation - enter two numbers: 1 2
1 / 2 = 0.5
1 * 2 = 2
上面的程序運行結果正常。另外程序的運行結果還有一種狀況,以下:
Test for invalid operation - enter two numbers: 1 0
Error 131: Divide by zero
呵呵!程序運行過程當中出現了異常(被0除),而且這種異常被程序預先定義的異常處理模塊所捕獲了。厲害吧!可千萬別輕視,這能夠C語言編寫的程序。
分析setjmp和longjmp
如今,來分析上面的程序的執行過程。固然,這裏主要分析在異常出現的狀況下,程序運行的控制轉移流程。因爲文章篇幅有限,分析時,咱們簡化不相關的代碼,這樣更也易理解控制流的執行過程。以下圖所示。
呵呵!如今是否對程序的執行流程一目瞭然,其中最關鍵的就是setjjmp和longjmp函數的調用處理。咱們分別來分析之。
當程序運行到第②步時,調用setjmp函數,這個函數會保存程序當前運行的一些狀態信息,主要是一些系統寄存器的值,如ss,cs,eip,eax, ebx,ecx,edx,eflags等寄存器,其中尤爲重要的是eip的值,由於它至關於保存了一個程序運行的執行點。這些信息被保存到mark變量中,這是一個C標準庫中所定義的特殊結構體類型的變量。
調用setjmp函數保存程序狀態以後,該函數返回0值,因而接下來程序執行到第③步和第④步中。在第④步中語句執行時,若是變量n2爲0值,因而便引起了一個浮點數計算異常,,致使控制流轉入fphandler函數中,也即進入到第⑤步。
而後運行到第⑥步,調用longjmp函數,這個函數內部會從先前的setjmp所保存的程序狀態,也即mark變量中,來恢復到之前的系統寄存器的值。因而便進入到了第⑦步,注意,這很是有點意思,實際上,經過longjmp函數的調用後,程序控制流(尤爲是eip的值)再次戲劇性地進入到了 setjmp函數的處理內部中,可是這一次setjmp返回的值是longjmp函數調用時,所傳入的第2個參數,也即-1,所以程序接下來進入到了第⑧步的執行之中。
總結
與goto語句不一樣,在C語言中,setjmp()與longjmp()的組合調用,爲程序員提供了一種更優雅的異常處理機制。它具備以下特色:
(1) goto只能實現本地跳轉,而setjmp()與longjmp()的組合運用,能有效的實現程序控制流的非本地(遠程)跳轉;
(2)與goto語句不一樣,setjmp()與longjmp()的組合運用,提供了真正意義上的異常處理機制。例如,它能有效定義受監控保護的模塊區域(相似於C++中try關鍵字所定義的區域);同時它也能有效地定義異常處理模塊(相似於C++中catch關鍵字所定義的區域);還有,它能在程序執行過程當中,經過longjmp函數的調用,方便地拋出異常(相似於C++中throw關鍵字)。
如今,相信你們已經對在C語言中提供的這種異常處理機制有了很全面地瞭解。可是咱們尚未深刻它研究它,下一篇文章中繼續探討吧!go!
上一篇文章對setjmp函數與longjmp函數有了較全面的瞭解,尤爲是這兩個函數的做用,函數所完成的功能,以及將setjmp函數與 longjmp函數組合起來,實現異常處理機制時,程序模塊控制流的執行過程等。這裏更深刻一步,將對setjmp與longjmp的具體使用方法和適用的場合,進行一個很是全面的闡述。
另外請特別注意,setjmp函數與longjmp函數老是組合起來使用,它們是緊密相關的一對操做,只有將它們結合起來使用,才能達到程序控制流有效轉移的目的,才能按照程序員的預先設計的意圖,去實現對程序中可能出現的異常進行集中處理。
與goto語句的做用相似,它能實現本地的跳轉
這種狀況容易理解,不過仍是列舉出一個示例程序吧!以下:
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行
// 判斷程序遠行中,是否出現錯誤,若是有錯誤,則跳轉!
if(1) longjmp(mark, 1);
// 其它代碼的執行
// 判斷程序遠行中,是否出現錯誤,若是有錯誤,則跳轉!
if(2) longjmp(mark, 2);
// 其它代碼的執行
// 判斷程序遠行中,是否出現錯誤,若是有錯誤,則跳轉!
if(-1) longjmp(mark, -1);
// 其它代碼的執行
}
else
{
// 錯誤處理模塊
switch (jmpret)
{
case 1:
printf( "Error 1n");
break;
case 2:
printf( "Error 2n");
break;
case 3:
printf( "Error 3n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}
return;
}
上面的例程很是地簡單,其中程序中使用到了異常處理的機制,這使得程序的代碼很是緊湊、清晰,易於理解。在程序運行過程當中,當異常狀況出現後,控制流是進行了一個本地跳轉(進入到異常處理的代碼模塊,是在同一個函數的內部),這種狀況其實也能夠用goto語句來予以很好的實現,可是,顯然 setjmp與longjmp的方式,更爲嚴謹一些,也更爲友善。程序的執行流如圖17-1所示。
setjmp與longjmp相結合,實現程序的非本地的跳轉
呵呵!這就是goto語句所不能實現的。也正由於如此,因此才說在C語言中,setjmp與longjmp相結合的方式,它提供了真正意義上的異常處理機制。其實上一篇文章中的那個例程,已經演示了longjmp函數的非本地跳轉的場景。這裏爲了更清晰演示本地跳轉與非本地跳轉,這二者之間的區別,咱們在上面剛纔的那個例程基礎上,進行很小的一點改動,代碼以下:
void Func1()
{
// 其它代碼的執行
// 判斷程序遠行中,是否出現錯誤,若是有錯誤,則跳轉!
if(1) longjmp(mark, 1);
}
void Func2()
{
// 其它代碼的執行
// 判斷程序遠行中,是否出現錯誤,若是有錯誤,則跳轉!
if(2) longjmp(mark, 2);
}
void Func3()
{
// 其它代碼的執行
// 判斷程序遠行中,是否出現錯誤,若是有錯誤,則跳轉!
if(-1) longjmp(mark, -1);
}
void main( void )
{
int jmpret;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行
// 下面的這些函數執行過程當中,有可能出現異常
Func1();
Func2();
Func3();
// 其它代碼的執行
}
else
{
// 錯誤處理模塊
switch (jmpret)
{
case 1:
printf( "Error 1n");
break;
case 2:
printf( "Error 2n");
break;
case 3:
printf( "Error 3n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}
return;
}
回顧一下,這與C++中提供的異常處理模型是否是很相近。異常的傳遞是能夠跨越一個或多個函數。這的確爲C程序員提供了一種較完善的異常處理編程的機制或手段。
setjmp和longjmp使用時,須要特別注意的事情
1、setjmp與longjmp結合使用時,它們必須有嚴格的前後執行順序,也即先調用setjmp函數,以後再調用longjmp函數,以恢復到先前被保存的「程序執行點」。不然,若是在setjmp調用以前,執行longjmp函數,將致使程序的執行流變的不可預測,很容易致使程序崩潰而退出。請看示例程序,代碼以下:
class Test
{
public:
Test() {printf("構造對象n");}
~Test() {printf("析構對象n");}
}obj;
//注意,上面聲明瞭一個全局變量obj
void main( void )
{
int jmpret;
// 注意,這裏將會致使程序崩潰,無條件退出
Func1();
while(1);
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行
// 下面的這些函數執行過程當中,有可能出現異常
Func1();
Func2();
Func3();
// 其它代碼的執行
}
else
{
// 錯誤處理模塊
switch (jmpret)
{
case 1:
printf( "Error 1n");
break;
case 2:
printf( "Error 2n");
break;
case 3:
printf( "Error 3n");
break;
default :
printf( "Unknown Error");
break;
}
exit(0);
}
return;
}
上面的程序運行結果,以下:
構造對象
Press any key to continue
的確,上面程序崩潰了,因爲在Func1()函數內,調用了longjmp,但此時程序尚未調用setjmp來 保存一個程序執行點。所以,程序的執行流變的不可預測。這樣致使的程序後果是很是嚴重的,例如說,上面的程序中,有一個對象被構造了,但程序崩潰退出時, 它的析構函數並無被系統來調用,得以清除一些必要的資源。因此這樣的程序是很是危險的。(另外請注意,上面的程序是一個C++程序,因此你們演示並測試這個例程時,把源文件的擴展名改成xxx.cpp)。
2、除了要求先調用setjmp函數,以後再調用longjmp函數(也即longjmp必須有對應的setjmp函數)以外。另外,還有一個很重要的規則,那就是longjmp的調用是有必定域範圍要求的。這未免太抽象了,仍是先看一個示例,以下:
int Sub_Func()
{
int jmpret, be_modify;
be_modify = 0;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行
}
else
{
// 錯誤處理模塊
switch (jmpret)
{
case 1:
printf( "Error 1n");
break;
case 2:
printf( "Error 2n");
break;
case 3:
printf( "Error 3n");
break;
default :
printf( "Unknown Error");
break;
}
//注意這一語句,程序有條件地退出
if (be_modify==0) exit(0);
}
return jmpret;
}
void main( void )
{
Sub_Func();
// 注意,雖然longjmp的調用是在setjmp以後,可是它超出了setjmp的做用範圍。
longjmp(mark, 1);
}
若是你運行或調試(單步跟蹤)一下上面程序,發現它真是挺神奇的,竟然longjmp執行時,程序還可以返回到setjmp的執行點,程序正常退出。可是這就說明了上面的這個例程的沒有問題嗎?咱們對這個程序小改一下,以下:
int Sub_Func()
{
// 注意,這裏改動了一點
int be_modify, jmpret;
be_modify = 0;
jmpret = setjmp( mark );
if( jmpret == 0 )
{
// 其它代碼的執行
}
else
{
// 錯誤處理模塊
switch (jmpret)
{
case 1:
printf( "Error 1n");
break;
case 2:
printf( "Error 2n");
break;
case 3:
printf( "Error 3n");
break;
default :
printf( "Unknown Error");
break;
}
//注意這一語句,程序有條件地退出
if (be_modify==0) exit(0);
}
return jmpret;
}
void main( void )
{
Sub_Func();
// 注意,雖然longjmp的調用是在setjmp以後,可是它超出了setjmp的做用範圍。
longjmp(mark, 1);
}
運行或調試(單步跟蹤)上面的程序,發現它崩潰了,爲何?這就是由於,「在調用setjmp的函數返回以前,調用longjmp,不然結果不可預料」(這在上一篇文章中已經提到過,MSDN中作了特別的說明)。爲何這樣作會致使不可預料?其實仔細想一想,緣由也很簡單,那就是由於,當 setjmp函數調用時,它保存的程序執行點環境,只應該在當前的函數做用域之內(或之後)纔會有效。若是函數返回到了上層(或更上層)的函數環境中,那麼setjmp保存的程序的環境也將會無效,由於堆棧中的數據此時將可能發生覆蓋,因此固然會致使不可預料的執行後果。
3、不要假象寄存器類型的變量將總會保持不變。在調用longjmp以後,經過setjmp所返回的控制流中,例程中寄存器類型的變量將不會被恢復。(MSDN中作了特別的說明,上一篇文章中,這也已經提到過)。寄存器類型的變量,是指爲了提升程序的運行效率,變量不被保存在內存中,而是直接被保存在寄存器中。寄存器類型的變量通常都是臨時變量,在C語言中,經過register定義,或直接嵌入彙編代碼的程序。這種類型的變量通常不多采用,因此在使用setjmp和longjmp時,基本上不用考慮到這一點。
4、MSDN中還作了特別的說明,「在C++程序中,當心對setjmp和longjmp的使用,由於setjmp和longjmp並不能很好地支持C++中面向對象的語義。所以在C++程序中,使用C++提供的異常處理機制將會更加安全。」雖說C++能很是好的兼容C,可是這並不是是100% 的徹底兼容。例如,這裏就是一個很好的例子,在C++程序中,它不能很好地與setjmp和longjmp和平共處。在後面的一些文章中,有關專門討論C ++如何兼容支持C語言中的異常處理機制時,會作詳細深刻的研究,這裏暫且跳過。
總結
主人公阿愚如今對setjmp與longjmp已是很是欽佩了,雖然它沒有C++中提供的異常處理模型那麼好用,可是畢竟在C語言中,有這麼好用的東東,已是很是不錯了。爲了更上一層樓,使setjmp與longjmp更接近C++中提供的異常處理模型(也即try()catch()語法)。阿愚找到了很多很是有價值的資料。不要錯過,繼續到下一篇文章中去吧!讓程序員朋友們「玩轉setjmp與longjmp」,Let’s go!
不要忘記,前面咱們得出過結論,C語言中提供的這種異常處理機制,與C++中的異常處理模型很類似。例如,能夠定義出相似的try block(受到監控的代碼);catch block(異常錯誤的處理模塊);以及能夠隨時拋出的異常(throw語句)。因此說,咱們能夠經過一種很是有技巧的封裝,來達到對setjmp和longjmp的使用方法(或者說語法規則),基本與C++中的語法一致。頗有誘惑吧!
首先展現阿愚封裝的在C語言環境中異常處理框架
1、首先是接口的頭文件,主要採用「宏」技術!代碼以下:
/*************************************************
* author: 王勝祥 *
* email: <mantx@21cn.com> *
* date: 2005-03-07 *
* version: *
* filename: ceh.h *
*************************************************/
/********************************************************************
This file is part of CEH(Exception Handling in C Language).
CEH is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
CEH is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
注意:這個異常處理框架不支持線程安全,不能在多線程的程序環境下使用。
若是您想在多線程的程序中使用它,您能夠本身試着來繼續完善這個
框架模型。
*********************************************************************/
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include <float.h>
#include <math.h>
#include <string.h>
////////////////////////////////////////////////////
/* 與異常有關的結構體定義 */
typedef struct _CEH_EXCEPTION {
int err_type; /* 異常類型 */
int err_code; /* 錯誤代碼 */
char err_msg[80]; /* 錯誤信息 */
}CEH_EXCEPTION; /* 異常對象 */
typedef struct _CEH_ELEMENT {
jmp_buf exec_status;
CEH_EXCEPTION ex_info;
struct _CEH_ELEMENT* next;
} CEH_ELEMENT; /* 存儲異常對象的鏈表元素 */
////////////////////////////////////////////////////
////////////////////////////////////////////////////
/* 內部接口定義,操縱維護鏈表數據結構 */
extern void CEH_push(CEH_ELEMENT* ceh_element);
extern CEH_ELEMENT* CEH_pop();
extern CEH_ELEMENT* CEH_top();
extern int CEH_isEmpty();
////////////////////////////////////////////////////
/* 如下是外部接口的定義 */
////////////////////////////////////////////////////
/* 拋出異常 */
extern void thrower(CEH_EXCEPTION* e);
/* 拋出異常 (throw)
a表示err_type
b表示err_code
c表示err_msg
*/
#define throw(a, b, c)
{
CEH_EXCEPTION ex;
memset(&ex, 0, sizeof(ex));
ex.err_type = a;
ex.err_code = b;
strncpy(ex.err_msg, c, sizeof(c));
thrower(&ex);
}
/* 從新拋出原來的異常 (rethrow)*/
#define rethrow thrower(ceh_ex_info)
////////////////////////////////////////////////////
////////////////////////////////////////////////////
/* 定義try block(受到監控的代碼)*/
#define try
{
int ___ceh_b_catch_found, ___ceh_b_occur_exception;
CEH_ELEMENT ___ceh_element;
CEH_EXCEPTION* ceh_ex_info;
memset(&___ceh_element, 0, sizeof(___ceh_element));
CEH_push(&___ceh_element);
ceh_ex_info = &___ceh_element.ex_info;
___ceh_b_catch_found = 0;
if (!(___ceh_b_occur_exception=setjmp(___ceh_element.exec_status)))
{
/* 定義catch block(異常錯誤的處理模塊)
catch表示捕獲全部類型的異常
*/
#define catch
}
else
{
CEH_pop();
___ceh_b_catch_found = 1;
/* end_try表示前面定義的try block和catch block結束 */
#define end_try
}
{
/* 沒有執行到任何的catch塊中 */
if(!___ceh_b_catch_found)
{
CEH_pop();
/* 出現了異常,但沒有捕獲到任何異常 */
if(___ceh_b_occur_exception) thrower(ceh_ex_info);
}
}
}
/* 定義catch block(異常錯誤的處理模塊)
catch_part表示捕獲必定範圍內的異常
*/
#define catch_part(i, j)
}
else if(ceh_ex_info->err_type>=i && ceh_ex_info->err_type<=j)
{
CEH_pop();
___ceh_b_catch_found = 1;
/* 定義catch block(異常錯誤的處理模塊)
catch_one表示只捕獲一種類型的異常
*/
#define catch_one(i)
}
else if(ceh_ex_info->err_type==i)
{
CEH_pop();
___ceh_b_catch_found = 1;
////////////////////////////////////////////////////
////////////////////////////////////////////////////
/* 其它可選的接口定義 */
extern void CEH_init();
////////////////////////////////////////////////////
2、另外還有一個簡單的實現文件,主要實現功能封裝。代碼以下:
/*************************************************
* author: 王勝祥 *
* email: <mantx@21cn.com> *
* date: 2005-03-07 *
* version: *
* filename: ceh.c *
*************************************************/
/********************************************************************
This file is part of CEH(Exception Handling in C Language).
CEH is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
CEH is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
注意:這個異常處理框架不支持線程安全,不能在多線程的程序環境下使用。
若是您想在多線程的程序中使用它,您能夠本身試着來繼續完善這個
框架模型。
*********************************************************************/
#include "ceh.h"
////////////////////////////////////////////////////
static CEH_ELEMENT* head = 0;
/* 把一個異常插入到鏈表頭中 */
void CEH_push(CEH_ELEMENT* ceh_element)
{
if(head) ceh_element->next = head;
head = ceh_element;
}
/* 從鏈表頭中,刪除並返回一個異常 */
CEH_ELEMENT* CEH_pop()
{
CEH_ELEMENT* ret = 0;
ret = head;
head = head->next;
return ret;
}
/* 從鏈表頭中,返回一個異常 */
CEH_ELEMENT* CEH_top()
{
return head;
}
/* 鏈表中是否有任何異常 */
int CEH_isEmpty()
{
return head==0;
}
////////////////////////////////////////////////////
////////////////////////////////////////////////////
/* 缺省的異常處理模塊 */
static void CEH_uncaught_exception_handler(CEH_EXCEPTION *ceh_ex_info)
{
printf("捕獲到一個未處理的異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
fprintf(stderr, "程序終止!n");
fflush(stderr);
exit(EXIT_FAILURE);
}
////////////////////////////////////////////////////
////////////////////////////////////////////////////
/* 拋出異常 */
void thrower(CEH_EXCEPTION* e)
{
CEH_ELEMENT *se;
if (CEH_isEmpty()) CEH_uncaught_exception_handler(e);
se = CEH_top();
se->ex_info.err_type = e->err_type;
se->ex_info.err_code = e->err_code;
strncpy(se->ex_info.err_msg, e->err_msg, sizeof(se->ex_info.err_msg));
longjmp(se->exec_status, 1);
}
////////////////////////////////////////////////////
////////////////////////////////////////////////////
static void fphandler( int sig, int num )
{
_fpreset();
switch( num )
{
case _FPE_INVALID:
throw(-1, num, "Invalid number" );
case _FPE_OVERFLOW:
throw(-1, num, "Overflow" );
case _FPE_UNDERFLOW:
throw(-1, num, "Underflow" );
case _FPE_ZERODIVIDE:
throw(-1, num, "Divide by zero" );
default:
throw(-1, num, "Other floating point error" );
}
}
void CEH_init()
{
_control87( 0, _MCW_EM );
if( signal( SIGFPE, fphandler ) == SIG_ERR )
{
fprintf( stderr, "Couldn"t set SIGFPEn" );
abort();
}
}
////////////////////////////////////////////////////
體驗上面設計出的異常處理框架
請花點時間仔細揣摩一下上面設計出的異常處理框架。呵呵!程序員朋友們,你們是否是發現它與C++提供的異常處理模型很是類似。例如,它提供的基本接口有 try、catch、以及throw等三條語句。仍是先看個具體例子吧!以便驗證一下這個C語言環境中異常處理框架是否真的比較好用。代碼以下:
#include "ceh.h"
int main(void)
{
//定義try block塊
try
{
int i,j;
printf("異常出現前nn");
// 拋出一個異常
// 其中第一個參數,表示異常類型;第二個參數表示錯誤代碼
// 第三個參數表示錯誤信息
throw(9, 15, "出現某某異常");
printf("異常出現後nn");
}
//定義catch block塊
catch
{
printf("catch塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
// 這裏稍有不一樣,須要定義一個表示當前的try block結束語句
// 它主要是清除相應的資源
end_try
}
注意,上面的測試程序但是C語言環境下的程序(文件的擴展名請使用.c結尾),雖然它看上去很像C++程序。請編譯運行一下,發現它是否是運行結果以下:
異常出現前
catch塊,被執行到
捕獲到一個異常,錯誤緣由是:出現某某異常! err_type:9 err_code:15
呵呵!程序的確是在按照咱們預想的流程在執行。再次提醒,這但是C程序,可是它的異常處理卻很是相似於C++中的風格,要知道,作到這一點其實很是地不容易。固然,上面異常對象的傳遞只是在一個函數的內部,一樣,它也適用於多個嵌套函數間的異常傳遞,仍是用代碼驗證一下吧!在上面的代碼基礎下,小小修改一點,代碼以下:
#include "ceh.h"
void test1()
{
throw(0, 20, "hahaha");
}
void test()
{
test1();
}
int main(void)
{
try
{
int i,j;
printf("異常出現前nn");
// 注意,這個函數的內部會拋出一個異常。
test();
throw(9, 15, "出現某某異常");
printf("異常出現後nn");
}
catch
{
printf("catch塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
end_try
}
一樣,在上面程序中,test1()函數內拋出的異常,能夠被上層main()函數中的catch block中捕獲到。運行結果就再也不給出了,你們能夠本身編譯運行一把,看看運行結果。
另外這個異常處理框架,與C++中的異常處理模型相似,它也支持try catch塊的多層嵌套。很厲害吧!仍是看演示代碼吧!,以下:
#include "ceh.h"
int main(void)
{
// 外層的try catch塊
try
{
// 內層的try catch塊
try
{
throw(1, 15, "嵌套在try塊中");
}
catch
{
printf("內層的catch塊被執行n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
printf("外層的catch塊被執行n");
}
end_try
throw(2, 30, "再拋一個異常");
}
catch
{
printf("外層的catch塊被執行n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
end_try
}
請編譯運行一下,程序的運行結果以下:
內層的catch塊被執行
捕獲到一個異常,錯誤緣由是:嵌套在try塊中! err_type:1 err_code:15
外層的catch塊被執行
捕獲到一個異常,錯誤緣由是:再拋一個異常! err_type:2 err_code:30
還有,這個異常處理框架也支持對異常的分類處理。這一點,也徹底是模仿C++中的異常處理模型。不過,因爲C語言中,不支持函數名重載,因此語法上略有不一樣,仍是看演示代碼吧!,以下:
#include "ceh.h"
int main(void)
{
try
{
int i,j;
printf("異常出現前nn");
throw(9, 15, "出現某某異常");
printf("異常出現後nn");
}
// 這裏表示捕獲異常類型從4到6的異常
catch_part(4, 6)
{
printf("catch_part(4, 6)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
// 這裏表示捕獲異常類型從9到10的異常
catch_part(9, 10)
{
printf("catch_part(9, 10)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
// 這裏表示只捕獲異常類型爲1的異常
catch_one(1)
{
printf("catch_one(1)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
// 這裏表示捕獲全部類型的異常
catch
{
printf("catch塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
end_try
}
請編譯運行一下,程序的運行結果以下:
異常出現前
catch_part(9, 10)塊,被執行到
捕獲到一個異常,錯誤緣由是:出現某某異常! err_type:9 err_code:15
與C++中的異常處理模型類似,它這裏的對異常的分類處理不只支持一維線性的;一樣,它也支持分層的,也即在當前的try catch塊中找不到相應的catch block,那麼它將會到上一層的try catch塊中繼續尋找。演示代碼以下:
#include "ceh.h"
int main(void)
{
try
{
try
{
throw(1, 15, "嵌套在try塊中");
}
catch_part(4, 6)
{
printf("catch_part(4, 6)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
end_try
printf("這裏將不會被執行到n");
}
catch_part(2, 3)
{
printf("catch_part(2, 3)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
// 找到了對應的catch block
catch_one(1)
{
printf("catch_one(1)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
catch
{
printf("catch塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
end_try
}
到目前爲止,你們是否是已經以爲,這個主人公阿愚封裝的在C語言環境中異常處理框架,已經與C++中的異常處理模型95%類似。不管是它的語法結構;仍是所完成的功能;以及它使用上的靈活性等。下面咱們來看一個各類狀況綜合的例子吧!代碼以下:
#include "ceh.h"
void test1()
{
throw(0, 20, "hahaha");
}
void test()
{
test1();
}
int main(void)
{
try
{
test();
}
catch
{
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
end_try
try
{
try
{
throw(1, 15, "嵌套在try塊中");
}
catch
{
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
end_try
throw(2, 30, "再拋一個異常");
}
catch
{
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
try
{
throw(0, 20, "嵌套在catch塊中");
}
catch
{
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
end_try
}
end_try
}
請編譯運行一下,程序的運行結果以下:
捕獲到一個異常,錯誤緣由是:hahaha! err_type:0 err_code:20
捕獲到一個異常,錯誤緣由是:嵌套在try塊中! err_type:1 err_code:15
捕獲到一個異常,錯誤緣由是:再拋一個異常! err_type:2 err_code:30
捕獲到一個異常,錯誤緣由是:嵌套在catch塊中! err_type:0 err_code:20
最後,爲了體會到這個異常處理框架,更進一步與C++中的異常處理模型類似。那就是它還支持異常的從新拋出,以及系統中能捕獲並處理程序中沒有catch到的異常。看代碼吧!以下:
#include "ceh.h"
void test1()
{
throw(0, 20, "hahaha");
}
void test()
{
test1();
}
int main(void)
{
// 這裏表示程序中將捕獲浮點數計算異常
CEH_init();
try
{
try
{
try
{
double i,j;
j = 0;
// 這裏出現浮點數計算異常
i = 1/j ;
test();
throw(9, 15, "出現某某異常");
}
end_try
}
catch_part(4, 6)
{
printf("catch_part(4, 6)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
catch_part(2, 3)
{
printf("catch_part(2, 3)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
}
// 捕獲到上面的異常
catch
{
printf("內層的catch塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
// 這裏再次把上面的異常從新拋出
rethrow;
printf("這裏將不會被執行到n");
}
end_try
}
catch_part(7, 9)
{
printf("catch_part(7, 9)塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
throw(2, 15, "出現某某異常");
}
// 再次捕獲到上面的異常
catch
{
printf("外層的catch塊,被執行到n");
printf("捕獲到一個異常,錯誤緣由是:%s! err_type:%d err_code:%dn",
ceh_ex_info->err_msg, ceh_ex_info->err_type, ceh_ex_info->err_code);
// 最後又拋出了一個異常,
// 可是這個異常沒有對應的catch block處理,因此係統中處理了
throw(2, 15, "出現某某異常");
}
end_try
}
請編譯運行一下,程序的運行結果以下:
內層的catch塊,被執行到
捕獲到一個異常,錯誤緣由是:Divide by zero! err_type:-1 err_code:131
外層的catch塊,被執行到
捕獲到一個異常,錯誤緣由是:Divide by zero! err_type:-1 err_code:131
捕獲到一個未處理的異常,錯誤緣由是:出現某某異常! err_type:2 err_code:15
程序終止!
goto語句實現程序執行中的近程跳轉(local jump),longjmp()和setjmp()函數實現程序執行中的遠程跳轉(nonlocaljump,也叫farjump)。一般你應該避免任何形式的執行中跳轉,由於在程序中使用goto語句或longjmp()函數不是一種好的編程習慣。
goto語句會跳過程序中的一段代碼並轉到一個預先指定的位置。爲了使用goto語句,你要預先指定一個有標號的位置做爲跳轉位置,這個位置必須與goto語句在同一個函數內。在不一樣的函數之間是沒法實現goto跳轉的。下面是一個使用goto語句的例子:
void bad_programmers_function(void)
{
int x
printf("Excuse me while I count to 5000... \n") ;
x----l~
while (1)
{
printf(" %d\n", x)
if (x ==5000)
goto all_done
else
x=x+1;
}
all_done:
prinft("Whew! That wasn't so bad, was it?\n");
}
若是不使用goto語句,是例能夠編寫得更好。下面就是一個改進了實現的例子:
void better_function (void)
{
int x
printf("Excuse me while I count to 5000... \n");
for (x=1; x<=5000, x++)
printf(" %d\n", x)
printf("Whew! That wasn't so bad, was it?\n") ;
}
前面已經提到,longjmp()和setjmp()函數實現程序執行中的遠程跳轉。當你在程序中調用setjmp()時,程序當前狀態將被保存到一個jmp_buf類型的結構中。此後,你能夠經過調用longjmp()函數恢復到調用setjmp()時的程序狀態。與goto語句不一樣,longjmp()和setjmp()函數實現的跳轉不必定在同一個函數內。然而,使用這兩個函數有一個很大的缺陷,當程序恢復到它原來所保存的狀態時,它將失去對全部在longjmp()和setjmp()之間動態分配的內存的控制,也就是說這將浪費全部在longjmp()和setjmp()之間用malloc()和calloc()分配所得的內存,從而使程序的效率大大下降。所以,你應該儘可能避免使用longjmp()和setjmp()函數,它們和goto語句同樣,都是不良編程習慣的表現。
下面是使用longjmp()函數和setjmp()函數的一個例子:
#i nclude <stdio.h>
#i nclude <setjmp.h>
#i nclude <stdlib.h>
jmp_buf saved_state;
void main(void);
void call_longjmp (void);
void main(void)
{
int ret_code;
printf("The current state of the program is being saved... \n");
ret_code = setjmp (saved_state);
printf("test point-----------------------");
if (ret_code ==1)
{
printf("The longjmp function has been called. \n" );
printf("The program's previous state has been restored. \n");
exit(0);
}
printf("I am about to call longjmp and\n");
printf("return to the previous program state... \n" );
call_longjmp();
}
void call_longjmp (void)
{
longjmp (saved_state, 1 );
}
}