異常簡述(一):C語言中的異常處理機制
人的一生會遇到很多大起大落,尤其是程式設計師.
程式設計師寫好的程式,論其消亡形式無非三種:無疾而終、自殺、他殺.
當然作為一名程式設計師,最樂意看到自己寫的程式能夠無疾而終,因此儘快的學習異常處理機制是非常重要的!
使自己的程式在遇到錯誤時能夠克服錯誤,更健壯,而不是遇到錯誤就憤憤自殺.
因此,在簡述C++的異常機制之前,本文先來簡述一下C語言中的異常處理機制.
在C語言中,傳統的錯誤處理方式有如下幾種:
1.直接終止程式(自殺)
例如:
?123456 | int main(){ int a = 10; int b = 20; int c = a/0; return 0; } |
當用gcc編譯完後,執行,會列印“浮點數例外”,然後程式結束.
這種情況是不允許的,無條件終止程式的庫無法運用到不能當機的程式裡。
2.返回一個錯誤的值,附加錯誤碼
這種錯誤處理方式也很常見,比如我們用C語言開啟一個檔案失敗時:
?123456789 | #include<stdio.h> #include<errno.h> int main(){ FILE * fp = fopen ( "test.txt" , "r" ); if (NULL == fp){ printf ( "檔案開啟失敗,錯誤碼:%d\n" , errno ); } return 0; } |
因為我當前處於Linux系統下,沒有GetLastError()函式,所以在Linux下使用全域性變數errno來演示.
這種情況,比較常用,但是有時不合適,例如返回錯誤碼是int,每個呼叫都要檢查錯誤值,極不方便,也容易讓程式規模加倍
3.返回一個合法的值,讓程式處於某種非法的狀態
這種例子,最常見的就是atoi函式,例如:
?123456789 | #include<stdio.h> #include<stdlib.h> int main(){ int a = atoi ( "123456789" ); int b = atoi ( "dasdasdcs" ); printf ( "a = %d\n" ,a); printf ( "b = %d\n" ,b); return 0; } |
雖然b為0,但是有一個全域性變量表示了這個0屬於非法值,不是由字串0轉過來的.
這種情況,很容易誤導呼叫者,萬一呼叫者沒有去檢查全域性變數errno或者通過其他方式檢查錯誤,那是一個災難,而且這種方式在併發的情況下不能很好工作
4.呼叫一個預先準備好在出現"錯誤"的情況下使用的函式.
這種異常處理情況比較少見,那我就現場直編了一個:
?12345678910111213141516171819 | #include<stdio.h> #include<stdlib.h> void DealError(){ printf ( "除數為0,老兄你在逗我?\n" ); } typedef void (*fun)(); int Div( int a, int b,fun callback){ if (b==0){ callback(); return 0; } return a/b; } int main(){ printf ( "正常情況下的4/2 = %d\n" ,Div(4,2,DealError)); printf ( "呼叫錯誤處理函式的4/0 = %d\n" ,Div(4,0,DealError)); return 0; } |
5、通過暴力的方式解決
暴力的方式有兩種,abort()函式和常見exit()函式.例如依然處理除0問題,程式碼可以這樣寫:
?123456789101112131415 | #include<stdio.h> #include<stdlib.h> int Div( int a, int b){ if (b==0){ //exit(1); //直接退出 abort (); //在Windows下會彈出一個資訊框 } return a/b; } int main(){ printf ( "正常情況下的4/2 = %d\n" ,Div(4,2)); printf ( "呼叫錯誤處理函式的4/0 = %d\n" ,Div(4,0)); return 0; } |
6、使用goto語句
雖然,goto語句十分強大,但違背了程式的順序執行,打亂的程式的執行流,盲目的使用goto語句可能會出現意想不到的錯誤,因此,並不推薦使用goto語句.
但,儘管如此,為了探索C語言的異常處理機制,我還是實現一下goto語句處理異常的程式碼.
?12345678910111213141516171819 | #include<stdio.h> #include<stdlib.h> int main(){ int a = 0; int b = 0; printf ( "請輸入兩個值:\n" ); printf ( "a = " ); scanf ( "%d" ,&a); printf ( "b = " ); scanf ( "%d" ,&b); if (b==0){ goto Error; } printf ( "a/b = %d\n" ,a/b); return 0; Error: printf ( "除數不能為0,程式異常退出!\n" ); exit (-1); } |
7、使用setjmp()與longjmp()
goto語句雖然可以跳來跳去,但標記與goto必須處於同一作用域內.
想從一個函式跳轉到另一個函式,就必須使用setjmp與longjmp組合.
例項如下:
?1234567891011121314151617181920212223 | #include<stdio.h> #include<setjmp.h> jmp_buf mark; int Div( int a, int b){ if (b==0){ longjmp (mark,1); //會使state = 1 } return a/b; } int main(){ int State = setjmp (mark); //儲存暫存器相關資訊,初始值為0 if (State==0){ Div(4,0); } else { switch (State){ case 1: printf ( "除0異常!\n" ); } } return 0; } |
注意事項:
1、setjmp必須先呼叫,在異常位置通過呼叫longjmp以恢復先前被儲存的程式執行點,否則將導致 不可預測的結果,甚至程式崩潰。
2、在呼叫setjmp的函式返回之前調動longjmp,否則結果不可預料。
setjmp與longjmp存在以下缺陷:
1、函式的使用者必須非常靠近函式呼叫的地方編寫錯誤處理程式碼,無疑使程式碼變的臃腫笨拙。
2、setjmp()和longjmp()並不能夠很好的支援C++面向物件的語義。
以上情況,便是C語言中的異常處理常見的機制,C++提供了更為完善的異常處理機制,我將在下文中簡述.