C語言中的異常處理

一 前言:ios

異常處理,對於作面向對象開發的開發者來講是再熟悉不過了,例如在C#中有設計模式

tryapp

{ide

     ...函數

}spa

catch( Exception e){...}設計

finally{調試

.....code

}orm

在C++中,咱們經常會使用

try{}

...

catch(){}

塊來進行異常處理。

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

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

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

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

"恢復模型":異常處理程序的工做是修正錯誤,而後從新嘗試調動出問題的方法,並認爲的二次能成功. 對於恢復模型,一般但願異常被處理以後能繼續執行程序.在這種狀況下,拋出異常更像是對方法的調用--能夠在Java裏用這種方法進行配置,以獲得相似恢復的行爲.(也就是說,不是拋出異常,而是調用方法修正錯誤.)或者,把try塊放在while循環裏,這樣就能夠不斷的進入try塊,直到獲得滿意的結果.


二 面向對象中的異常處理

大體瞭解了什麼是異常處理後,因爲異常處理在面嚮對象語言中使用的比較廣泛,咱們就先以C++爲例,作一個關於異常處理的簡單例子:

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

這裏,隱藏這一個錯誤,那就是當除數爲0時,會出現,因此,咱們得使用異常處理來捕捉這個異常,並拋出異常信息。

具體看代碼:

 

 1  #include  < iostream >
 2  #include  < exception >
 3  using   namespace  std;
 4  class  DivideError: public  exception
 5  {
 6    public :
 7            DivideError::DivideError():exception(){} 
 8            const   char *  what(){
 9               return   " 試圖去除一個值爲0的數字 " ;
10          }
11 
12  };
13  double  quotion( int  numerator, int  denominator)
14  {
15       if ( 0 == denominator)           // 當除數爲0時,拋出異常 
16       throw  DivideError();
17       return  static_cast < double > (numerator) / denominator;    
18  }
19  int  main()
20  {
21       int  number1;              // 第一個數字
22       int  number2;              // 第二個數字
23       double  result;
24      cout << " 請輸入兩個數字: "  ;
25       while (cin >> number1 >> number2){
26           try {
27              result = quotion(number1,number2);
28              cout << " 結果是 : " << result << endl;
29              
30          }      // end try
31           catch (DivideError  & divException){
32              cout << " 產生異常: "
33                   << divException.what() << endl;
34          }
35      }
36      
37  }
38 

 

 

在這個例子中,咱們使用了<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()函數進行異常終止:

 

 1  #include  < stdio.h >
 2  #include  < stdlib.h >
 3  double  diva( double  num1, double  num2)          // 兩數相除函數 
 4  {
 5       double  re;
 6      re = num1 / num2;
 7       return  re;
 8  }
 9  int  main()
10  {
11      double  a,b,result;
12   printf( " 請輸入第一個數字: " );
13    scanf( " %lf " , & a);
14    printf( " 請輸入第二個數字: " );
15    scanf( " %lf " , & b);
16     if ( 0 == b)                                 // 若是除數爲0終止程序 
17    exit(EXIT_FAILURE);
18  result = diva(a,b);
19     printf( " 相除的結果是: %.2lf\n " ,result);    
20  return   0 ;
21  }

其中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(),它的原型是這樣的:int atexit(atexit_t func);

具體看以下程序:

 

 1  #include  < stdio.h >
 2  #include  < stdlib.h >
 3  void  Exception( void )                            // 註冊終止函數,經過掛接到此函數,捕獲異常信息 
 4  {
 5      printf( " 試圖去除以一個爲0的數字,出現異常!\n " );
 6  }
 7  int  main()
 8 
 9      double  a,b,result;
10    printf( " 請輸入第一個數字: " );
11    scanf( " %lf " , & a);
12    printf( " 請輸入第二個數字: " );
13    scanf( " %lf " , & b);
14     if ( 0 == b)                     // 若是除數爲0終止程序 ,並掛接到模擬異常捕獲的註冊函數
15    {
16        
17    atexit(Exception);                          
18    exit(EXIT_FAILURE);
19    } 
20     result = diva(a,b);
21     printf( " 相除的結果是: %.2lf\n " ,result);    
22  return   0 ;
23  }

這裏須要注意的是,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的值,結果是二、

當文件存在時,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 ;
}

 

 

四 總結:

 

除了以上幾種方法以外,另外還有使用信號量等等方法進行異常處理。固然在實際開發中每一個人都有各類調式的技巧,並且這文章並非說明異常處理必定要這樣作,這只是對通常作法的一些總結,也不要亂使用異常處理,若是弄的很差就嚴重影響了程序的效率和結構,就像設計模式同樣,不能胡亂使用。

相關文章
相關標籤/搜索