C語言中的異常處理

來源:互聯網
上載者:User

一 前言:

異常處理,對於做物件導向開發的開發人員來說是再熟悉不過了,例如在C#中有

try

{

     ...

}

catch( Exception e){...}

finally{

.....

}

在C++中,我們常常會使用

try{}

...

catch(){}

塊來進行異常處理。

說了那麼多,那麼到底什麼是異常處理呢。

異常處理(又稱為錯誤處理)功能提供了處理常式運行時出現的任何意外或異常情況的方法。

異常處理一般有兩種模型,一種是"終止模型",一種是"恢複模型"

"終止模型":在這種模型中,將假設錯誤非常關鍵,將以致於程式無法返回到異常發生的地方繼續執行.一旦異常被拋出,就表明錯誤已無法挽回,也不能回來繼續執行.

"恢複模型":例外處理常式的工作是修正錯誤,然後重新嘗試調動出問題的方法,並認為的二次能成功. 對於恢複模型,通常希望異常被處理之後能繼續執行程式.在這種情況下,拋出異常更像是對方法的調用--可以在Java裡用這種方法進行配置,以得到類似恢複的行為.(也就是說,不是拋出異常,而是調用方法修正錯誤.)或者,把try塊放在while迴圈裡,這樣就可以不斷的進入try塊,直到得到滿意的結果.

二 物件導向中的異常處理

大致瞭解了什麼是異常處理後,由於異常處理在物件導向語言中使用的比較普遍,我們就先以C++為例,做一個關於異常處理的簡單例子:

問題:求兩個數相除的結果。

這裡,隱藏這一個錯誤,那就是當除數為0時,會出現,所以,我們得使用異常處理來捕捉這個異常,並拋出異常資訊。

具體看代碼:

#include <iostream> #include <exception> using namespace std; class DivideError:public exception {  public:           DivideError::DivideError():exception(){}           const char* what(){             return "試圖去除一個值為0的數字";         }  }; double quotion(int numerator,int denominator) {     if(0==denominator)          //當除數為0時,拋出異常      throw DivideError();     return static_cast<double>(numerator)/denominator;     } int main() {     int number1;             //第一個數字     int number2;             //第二個數字     double result;     cout<<"請輸入兩個數字:" ;     while(cin>>number1>>number2){         try{             result=quotion(number1,number2);             cout<<"結果是 :"<<result<<endl;                      }     //end try         catch(DivideError &divException){             cout<<"產生異常:"                 <<divException.what()<<endl;         }     }      }


 

在這個例子中,我們使用了<expection>標頭檔中的exception類,並使DivideError類繼承了它,同時重載了虛方法what(),以給出特定的異常資訊。

而C#中的異常處理類則封裝的更有全面,裡面封裝了常用的異常處理資訊,這裡就不多說了。

 

 

三 C語言中的異常處理

 在C語言中異常處理一般有這麼幾種方式:

1.使用標準C庫提供了abort()和exit()兩個函數,它們可以強行終止程式的運行,其聲明處於<stdlib.h>標頭檔中。

2.使用assert(斷言)宏調用,位於標頭檔<assert.h>中,當程式出錯時,就會引發一個abort()。

3.使用errno全域變數,由C執行階段程式庫函數提供,位於標頭檔<errno.h>中。

4.使用goto語句,當出錯時跳轉。

5.使用setjmp,longjmp進行異常處理。

接下來,我們就依次對這幾種方式來看看到底是怎麼做的:

我們仍舊以前面處理除數為0的異常為例子。

1.使用exit()函數進行異常終止:

 

#include <stdio.h> #include <stdlib.h> double diva(double num1,double num2)         //兩數相除函數  {     double re;     re=num1/num2;     return re; } int main() {    double a,b,result;  printf("請輸入第一個數字:");   scanf("%lf",&a);   printf("請輸入第二個數字:");   scanf("%lf",&b);   if(0==b)                                //如果除數為0終止程式    exit(EXIT_FAILURE); result=diva(a,b);    printf("相除的結果是: %.2lf\n",result);     return 0; }


其中exit的定義如下:

_CRTIMP void __cdecl __MINGW_NOTHROW exit (int) __MINGW_ATTRIB_NORETURN;

exit的函數原型:void exit(int)由此,我們也可以知道EXIT_FAILURE宏應該是一個整數,exit()函數的傳遞參數是兩個宏,一個是剛才看到的EXIT_FAILURE,還有一個是EXIT_SUCCESS從字面就可以看出一個是出錯後強制終止程式,而一個是程式正常結束。他們的定義是:

#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

到此,當出現異常的時候,程式是終止了,但是我們並沒有捕獲到異常資訊,要捕獲異常資訊,我們可以使用註冊終止函數atexit(),它的原型是這樣的:intatexit(atexit_t func);

具體看如下程式:

#include <stdio.h> #include <stdlib.h> void Exception(void)                           //註冊終止函數,通過掛接到此函數,捕獲異常資訊  {     printf("試圖去除以一個為0的數字,出現異常。\n"); } int main() {     double a,b,result;   printf("請輸入第一個數字:");   scanf("%lf",&a);   printf("請輸入第二個數字:");   scanf("%lf",&b);   if(0==b)                    //如果除數為0終止程式 ,並掛接到類比異常捕獲的註冊函數   {          atexit(Exception);                             exit(EXIT_FAILURE);   }     result=diva(a,b);    printf("相除的結果是: %.2lf\n",result);     return 0; }


這裡需要注意的是,atexit()函數總是被執行的,就算沒有exit()函數,當程式結束時也會被執行。並且,可以掛接多個註冊函數,按照堆棧結構進行執行。abort()函數與exit()函數類似,當出錯時,能使得程式正常退出,這裡就不多說了。

2.使用assert()進行異常處理:

assert()是一個偵錯工具時經常使用的宏,切記,它不是一個函數,在程式運行時它計算括弧內的運算式,如果運算式為FALSE  (0),  程式將報告錯誤,並終止執行。如果運算式不為0,則繼續執行後面的語句。這個宏通常原來判斷程式中是否出現了明顯非法的資料,如果出現了終止程式以免導致嚴重後果,同時也便於尋找錯誤。  
另外需要注意的是:assert只有在Debug版本中才有效,如果編譯為Release版本則被忽略。

我們就前面的問題,使用assert斷言進行異常終止操作:構造可能出現出錯的斷言運算式:assert(number。=0)這樣,當除數為0的時候,運算式就為false,程式報告錯誤,並終止執行。

代碼如下:

#include <stdio.h>#include <assert.h>double diva(double num1,double num2)         //兩數相除函數 {    double re;    re=num1/num2;    return re;}int main(){  printf("請輸入第一個數字:");  scanf("%lf",&a);  printf("請輸入第二個數字:");  scanf("%lf",&b);  assert(0!=b);                                //構造斷言運算式,捕獲預期異常錯誤   result=diva(a,b);   printf("相除的結果是: %.2lf\n",result);       return 0;}


3.使用errno全域變數,進行異常處理:

errno全域變數主要在調式中,當系統API函數發生異常的時候,將errno變數賦予一個整數值,根據查看這個值來推測出錯的原因。

其中的各個整數值都有一個相應的宏定義,表示不同的異常原因:

代碼 #define EPERM        1    /* Operation not permitted */#define    ENOFILE        2    /* No such file or directory */#define    ENOENT        2#define    ESRCH        3    /* No such process */#define    EINTR        4    /* Interrupted function call */#define    EIO        5    /* Input/output error */#define    ENXIO        6    /* No such device or address */#define    E2BIG        7    /* Arg list too long */#define    ENOEXEC        8    /* Exec format error */#define    EBADF        9    /* Bad file descriptor */#define    ECHILD        10    /* No child processes */#define    EAGAIN        11    /* Resource temporarily unavailable */#define    ENOMEM        12    /* Not enough space */#define    EACCES        13    /* Permission denied */#define    EFAULT        14    /* Bad address *//* 15 - Unknown Error */#define    EBUSY        16    /* strerror reports "Resource device" */#define    EEXIST        17    /* File exists */#define    EXDEV        18    /* Improper link (cross-device link?) */#define    ENODEV        19    /* No such device */#define    ENOTDIR        20    /* Not a directory */#define    EISDIR        21    /* Is a directory */#define    EINVAL        22    /* Invalid argument */#define    ENFILE        23    /* Too many open files in system */#define    EMFILE        24    /* Too many open files */#define    ENOTTY        25    /* Inappropriate I/O control operation *//* 26 - Unknown Error */#define    EFBIG        27    /* File too large */#define    ENOSPC        28    /* No space left on device */#define    ESPIPE        29    /* Invalid seek (seek on a pipe?) */#define    EROFS        30    /* Read-only file system */#define    EMLINK        31    /* Too many links */#define    EPIPE        32    /* Broken pipe */#define    EDOM        33    /* Domain error (math functions) */#define    ERANGE        34    /* Result too large (possibly too small) *//* 35 - Unknown Error */#define    EDEADLOCK    36    /* Resource deadlock avoided (non-Cyg) */#define    EDEADLK        36/* 37 - Unknown Error */#define    ENAMETOOLONG    38    /* Filename too long (91 in Cyg?) */#define    ENOLCK        39    /* No locks available (46 in Cyg?) */#define    ENOSYS        40    /* Function not implemented (88 in Cyg?) */#define    ENOTEMPTY    41    /* Directory not empty (90 in Cyg?) */#define    EILSEQ        42    /* Illegal byte sequence */


這裡我們就不以前面的除數為0的例子來進行異常處理了,因為我不知道如何定義自己特定錯誤的errno,如果哪位知道,希望能給出方法。我以一個網上的例子來說明它的使用方法:

代碼 #include <errno.h>  #include <math.h>  #include <stdio.h>  int main(void)  {  errno = 0;  if (NULL == fopen("d:\\1.txt", "rb"))  {  printf("%d", errno);  }  else  {   printf("%d", errno);  }  return 0;  }


 

這裡試圖開啟一個d盤的檔案,如果檔案不存在,這是查看errno的值,結果是2、

當檔案存在時,errno的值為初始值0。然後查看值為2的錯誤資訊,在宏定義那邊#define    ENOFILE        2    /* No such file or directory */
便知道錯誤的原因了。

4.使用goto語句進行異常處理:

goto語句相信大家都很熟悉,是一個跳躍陳述式,我們還是以除數為0的例子,來構造一個異常處理的例子:

代碼 #include <stdio.h>double diva(double num1,double num2)         //兩數相除函數 {    double re;    re=num1/num2;    return re;}int main(){  int tag=0;  double a,b,result;  if(1==tag)  {      Throw:    printf("除數為0,出現異常\n");  }   tag=1;  printf("請輸入第一個數字:");  scanf("%lf",&a);  printf("請輸入第二個數字:");  scanf("%lf",&b);if(b==0)                                   //捕獲異常(或許這麼說並不恰當,暫且這麼理解)  goto Throw;                                //拋出異常   result=diva(a,b);   printf("%d\n",errno);   printf("相除的結果是: %.2lf\n",result);    return 0;}


 

5.使用setjmp和longjmp進行異常捕獲與處理:

setjmp和longjmp是非局部跳轉,類似goto跳轉作用,但是goto語句具有局限性,只能在局部進行跳轉,當需要跳轉到非一個函數內的地方時就需要用到setjmp和longjmp。setjmp函數用於儲存程式的運行時的堆棧環境,接下來的其它地方,你可以通過調用longjmp函數來恢複先前被儲存的程式堆棧環境。異常處理基本方法:

使用setjmp設定一個跳轉點,然後在程式其他地方調用longjmp跳轉到該點(拋出異常).

代碼如下所示:

#include <stdio.h>#include <setjmp.h>jmp_buf j;void Exception(void){   longjmp(j,1);} double diva(double num1,double num2)         //兩數相除函數 {    double re;     re=num1/num2;    return re;} int main(){    double a,b,result;       printf("請輸入第一個數字:");   scanf("%lf",&a);   printf("請輸入第二個數字:");  if(setjmp(j)==0)  {   scanf("%lf",&b);   if(0==b)   Exception(); result=diva(a,b);    printf("相除的結果是: %.2lf\n",result);  }  else  printf("試圖除以一個為0的數字\n"); return 0;}


 

四 總結:

 

除了以上幾種方法之外,另外還有使用訊號量等等方法進行異常處理。當然在實際開發中每個人都有各種調式的技巧,而且這文章並不是說明異常處理一定要這樣做,這隻是對一般做法的一些總結,也不要亂使用異常處理,如果弄的不好就嚴重影響了程式的效率和結構,就像設計模式一樣,不能胡亂使用。

 








































































相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.