傳統C語言異常處理機制
傳統的C語言異常處理,一般情況下為被調用者通過設定不同的傳回值或者設定一個表示錯誤的全域變數值,以代表執行正常或者是發生了各種各樣的錯誤,異常,警告等等。而調用者則通過被調用者的傳回值來判斷是否發生了異常,並對異常進行處理。以下是這種處理方式的簡單樣本:
fun1(){....int result=fun2();switch(result){case 1 ....case 2 ........}....}fun2(){.... .... return -1;........ return 1;....}
更加合理的異常處理機制
在傳統的C語言異常處理機制中,若需要按照層次傳遞異常,則每一層都要判斷每一個下層介面的傳回值,並編寫相應的錯誤處理代碼。本層無法處理的錯誤還要上傳至上層介面處理。這樣一層一層的調用,當層次較多時,錯誤會向上逐層傳遞,需要編寫大量代碼,給程式員造成不小的麻煩並且很容易出錯。此外大量錯誤處理代碼還會降低介面主要流程的可讀性。雖然經過不斷的檢查與調試,發生錯誤是小機率事件,卻需要每層都進行判斷,影響效率。
因此,我們需要反思一下,在大規模C工程中,我們到底需要什麼樣的異常處理機制?例如JAVA,C++中的try/catch/throw異常處理機制更加有效,當發生錯誤後能夠立即跳轉到有能力處理該錯誤的高層介面,而無需逐層傳遞該異常。
具體的實現細節
在C語言中,我們可以通過setjmp/longjmp實現與C++異常處理相類似的機制。
他們的原型為:int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
使用setjmp/longjmp必須要在程式中添加setjmp.h這個標頭檔。setjmp()的作用是在緩衝區envbuf中儲存系統堆棧中的內容,該宏的傳回值為0。而longjmp()使程式跳轉到最近一次調用setjmp()處,然後重新執行下面的語句。但是longjmp()的第二個參數會傳遞一個值遞給setjmp(),這個值就是調用longjmp()後再次執行setjmp()的傳回值,這樣就利用不同的傳回值與第一次調用setjmp()區別開。
需要說明的是setjmp/longjmp可以跨函數,因此可以利用他們實現跨函數調用層次的異常處理機制。
下面是一個很簡單的使用例子:
我們有3個小檔案分別為main.c file1.c file2.c,下面列出他們:
int main(){jmp_buf main_stat;if(setjmp(error_stat)!=0){printf(" error in main \n");return -1;}file1();return 0;}
int file1(){jmp_buf file1_stat;memcpy(&file1_stat,&error_stat,sizeof(file1_stat));if(setjmp(error_stat)!=0){printf(" error in file1 \n");memcpy(&error_stat,&file1_stat,sizeof(file1_stat));longjmp(error_stat,1);}file2();}
int file2(){jmp_buf file2_stat;memcpy(&file2_stat,&error_stat,sizeof(file2_stat));if(setjmp(error_stat)!=0){printf(" error in file2 \n");memcpy(&error_stat,&file2_stat,sizeof(file2_stat));longjmp(error_stat,1);}printf(" start error \n");longjmp(error_stat,1);}
對他們進行編譯,運行,輸出結果為:
start error
error in file2
error in file1
error in main
這隻是一個很簡單的例子,而且是按照層次傳遞的,不按照層次傳遞的代碼與之類似。
小結
通過使用setjmp/longjmp,可以在C語言中實現模仿C++層次傳遞的異常處理機制。這種實現方式效率比較高,在大規模工程項目中能夠很好的實現異常處理,減輕程式的複雜度。