1. 程式人生 > >異常簡述(一):C語言中的異常處理機制

異常簡述(一):C語言中的異常處理機制

  人的一生會遇到很多大起大落,尤其是程式設計師.

  程式設計師寫好的程式,論其消亡形式無非三種:無疾而終、自殺、他殺.

  當然作為一名程式設計師,最樂意看到自己寫的程式能夠無疾而終,因此儘快的學習異常處理機制是非常重要的!

  使自己的程式在遇到錯誤時能夠克服錯誤,更健壯,而不是遇到錯誤就憤憤自殺.

  因此,在簡述C++的異常機制之前,本文先來簡述一下C語言中的異常處理機制.

  在C語言中,傳統的錯誤處理方式有如下幾種:

1.直接終止程式(自殺)

  例如:

?
123456int 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);   //儲存暫存器相關資訊,初始值為0if(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++提供了更為完善的異常處理機制,我將在下文中簡述.