Visual C++中的异常处理浅析(上)
生活随笔
收集整理的這篇文章主要介紹了
Visual C++中的异常处理浅析(上)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Visual C++中的異常處理淺析作者:宋寶華? e-mail:[email]21cnbao@21cn.com[/email]Visual C++提供了對C語言、C++語言及MFC的支持,因而其涉及到的異常(exception)處理也包含了這三種類型,即C語言、C++語言和MFC的異常處理。除此之外,微軟對C和C++的異常處理進行了擴展,提出了結構化異常處理(SEH)的概念,它支持C和C++(與之相比,MFC異常處理僅支持C++)。一個典型的異常處理包含如下幾個步驟:(1)程序執行時發生錯誤;(2)以一個異常對象(最簡單的是一個整數)記錄錯誤的原因及相關信息;(3)程序檢測到這個錯誤(讀取異常對象);(4)程序決定如何處理錯誤;(5)進行錯誤處理,并在此后恢復/終止程序的執行。C、C++、MFC及SEH在這幾個步驟中表現出了不同的特點。本文將對這四種異常處理進行介紹,并對它們進行對比分析。本文例程的調試平臺為Visual C++6.0,操作系統為Windows XP,所有程序均調試通過。在進入正式的講解之前,先說幾句廢話。許多的編程新手對異常處理視而不見,程序里很少考慮異常情況。一部分人甚至根本就不考慮,以為程序總是能以正確的途徑運行。譬如我們有的程序設計者調用fopen打開一個文件后,立馬就開始進行讀寫操作,根本就不考慮文件是否正常打開了。這種習慣一定要改掉,縱使你再不愿意!這是軟件健壯性的需要!異常處理不是浪費時間!1.C語言異常處理1.1 異常終止標準C庫提供了abort()和exit()兩個函數,它們可以強行終止程序的運行,其聲明處于<stdlib.h>頭文件中。這兩個函數本身不能檢測異常,但在C程序發生異常后經常使用這兩個函數進行程序終止。下面的這個例子描述了exit()的行為:#include <stdio.h>#include <stdlib.h>int main(void){??? exit(EXIT_SUCCESS);??? printf("程序不會執行到這里\n");??? return 0;}在這個例子中,main函數一開始就執行了exit函數(此函數原型為void exit(int)),因此,程序不會輸出"程序不會執行到這里"。程序中的exit(EXIT_SUCCESS)表示程序正常結束,與之對應的exit(EXIT_FAILURE)表示程序執行錯誤,只能強行終止。EXIT_SUCCESS、EXIT_FAILURE分別定義為0和1。對于exit函數,我們可以利用atexit函數為exit事件“掛接”另外的函數,這種“掛接”有點類似Windows編程中的“鉤子”(Hook)。譬如:#include <stdio.h>#include <stdlib.h>static void atExitFunc(void){??? printf("atexit掛接的函數\n");}int main(void){??? atexit(atExitFunc);??? exit(EXIT_SUCCESS);??? printf("程序不會執行到這里\n");??? return 0;}程序輸出"atexit掛接的函數"后即終止。來看下面的程序,我們不調用exit函數,看看atexit掛接的函數會否執行:#include <stdio.h>#include <stdlib.h>static void atExitFunc(void){? printf("atexit掛接的函數\n");}int main(void){? atexit(atExitFunc);? //exit(EXIT_SUCCESS);? printf("不調用exit函數\n");? return 0;}程序輸出:不調用exit函數atexit掛接的函數這說明,即便是我們不調用exit函數,當程序本身退出時,atexit掛接的函數仍然會被執行。atexit可以被多次執行,并掛接多個函數,這些函數的執行順序為后掛接的先執行,例如:#include <stdio.h>#include <stdlib.h>static void atExitFunc1(void){? printf("atexit掛接的函數1\n");}static void atExitFunc2(void){? printf("atexit掛接的函數2\n");}static void atExitFunc3(void){? printf("atexit掛接的函數3\n");}int main(void){? atexit(atExitFunc1);? atexit(atExitFunc2);? atexit(atExitFunc3);? return 0;}輸出的結果是:atexit掛接的函數3atexit掛接的函數2atexit掛接的函數1在Visual C++中,如果以abort函數(此函數不帶參數,原型為void?? abort(void))終止程序,則會在debug模式運行時彈出如圖1所示的對話框。圖1 以abort函數終止程序1.2 斷言assert宏在C語言程序的調試中發揮著重要的作用,它用于檢測不會發生的情況,表明一旦發生了這樣的情況,程序就實際上執行錯誤了,例如strcpy函數:char *strcpy(char *strDest, const char *strSrc){? char *address = strDest;? assert((strDest != NULL) && (strSrc != NULL));? while ((*strDest++ =? *strSrc++) != '\0')??? ;? return address;}其中包含斷言assert( (strDest != NULL) && (strSrc != NULL) ),它的意思是源和目的字符串的地址都不能為空,一旦為空,程序實際上就執行錯誤了,會引發一個abort。assert宏的定義為:#ifdef NDEBUG? #define assert(exp)???? ((void)0)#else? #ifdef __cplusplus??? extern "C"??? {??? #endif??? _CRTIMP void __cdecl _assert(void *, void *, unsigned);??? #ifdef __cplusplus??? }? #endif? #define assert(exp) (void)( (exp) || (_assert(#exp, __FILE__, __LINE__), 0) )#endif /* NDEBUG */??? 如果程序不在debug模式下,assert宏實際上什么都不做;而在debug模式下,實際上是對_assert()函數的調用,此函數將輸出發生錯誤的文件名、代碼行、條件表達式。例如下列程序:#include <stdio.h>#include <stdlib.h>#include <assert.h>char * myStrcpy( char *strDest, const char *strSrc )? {?? ??? char *address = strDest;??? assert( (strDest != NULL) && (strSrc != NULL) );??? while( (*strDest++ = *strSrc++) != '\0' );??? return address;}int main(void){??? myStrcpy(NULL,NULL);??? return 0;}在此程序中,為了避免我們的strcpy與C庫中的strcpy重名,將其改為myStrcpy。程序的輸出如圖2:
圖2 assert的輸出圖2 assert的輸出失敗的斷言也會彈出如圖1所示的對話框,這是因為_assert()函數中也調用了abort()函數。一定要記住的是assert本質上是一個宏,而不是一個函數,因而不能把帶有副作用的表達式放入assert的“參數”中。1.3 errno?? errno在C程序中是一個全局變量,這個變量由C運行時庫函數設置,用戶程序需要在程序發生異常時檢測之。C運行庫中主要在math.h和stdio.h頭文件聲明的函數中使用了errno,前者用于檢測數學運算的合法性,后者用于檢測I/O操作中(主要是文件)的錯誤,例如:#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;}在此程序中,如果文件打開失敗(fopen返回NULL),證明發生了異常。我們讀取error可以獲知錯誤的原因,如果D盤根目錄下不存在“1.txt”文件,將輸出2,表示文件不存在;在文件存在并正確打開的情況下,將執行到else語句,輸出0,證明errno沒有被設置。Visual C++提供了兩種版本的C運行時庫。—個版本供單線程應用程序調用,另一個版本供多線程應用程序調用。多線程運行時庫與單線程運行時庫的一個重大差別就是對于類似errno的全局變量,每個線程單獨設置了一個。因此,對于多線程的程序,我們應該使用多線程C運行時庫,才能獲得正確的error值。另外,在使用errno之前,我們最好將其設置為0,即執行errno = 0的賦值語句。1.4 其它除了上述異常處理方式外,在C語言中還支持非局部跳轉(使用setjmp和longjmp)、信號(使用signal、raise)、返回錯誤值或回傳錯誤值給參數等方式進行一定能力的異常處理,但是其使用不如1.1~1.3節所介紹方式常用,我們不必過細研究。?從以上分析可知,C語言的異常處理是簡單而不全面的。與C++的異常處理比起來,C語言異常處理相形見絀,它就像娘胎里的雛嬰。?2.C++語言異常處理2.1 C++異常處理語法感謝C++語言的后期改造者們,他們在標準C++語言中專門集成了異常處理的相關語法(與之不同的是,所有的C 標準庫異常體系都需要運行庫的支持,它不是語言內核支持的)。當然,異常處理被加到程序設計語言中,也是程序語言發展和逐步完善的必然結果。我們看到,C++不是唯一集成異常處理的語言。C++的異常處理結構為:try{//可能引發異常的代碼}catch(type_1 e){// type_1類型異常處理}catch(type_2 e){// type_2類型異常處理}catch (...)//會捕獲所有未被捕獲的異常,必須最后出現{}而異常的拋出方式為使用throw(type e),try、catch和throw都是C++為處理異常而添加的關鍵字。看看這個例子:#include <stdio.h>//定義Point結構體(類)typedef struct tagPoint{? int x;? int y;} Point;//扔出int異常的函數static void f(int n){? throw 1;}//扔出Point異常的函數static void f(Point point){? Point p;? p.x = 0;? p.y = 0;? throw p;}int main(){? Point point;? point.x = 0;? point.y = 0;? try? {??? f(point); //拋出Point異常??? //f(1);?? //拋出int異常? }? catch (int e)? {??? printf("捕獲到int異常:%d\n", e);? }? catch (Point e)? {??? printf("捕獲到Point異常:(%d,%d)\n", e.x, e.y);? }? return 0;}函數f定義了兩個版本:f(int)和f(Point),分別拋出int和Point異常。當main函數的try{…}中調用f(point)時和f(1)時,分別輸出:捕獲到Point異常:(0,0)和捕獲到int異常:1在C++中,throw拋出異常的特點有:(1)可以拋出基本數據類型異常,如int和char等;(2)可以拋出復雜數據類型異常,如結構體(在C++中結構體也是類)和類;(3)C++的異常處理必須由調用者主動檢查。一旦拋出異常,而程序不捕獲的話,那么abort()函數就會被調用,彈出如圖1所示的對話框,程序被終止;(4)可以在函數頭后加throw([type-ID-list])給出異常規格,聲明其能拋出什么類型的異常。type-ID-list是一個可選項,其中包括了一個或多個類型的名字,它們之間以逗號分隔。如果函數沒有異常規格指定,則可以拋出任意類型的異常。2.2 標準異常下面給出了C++提供的一些標準異常:namespace std{??? //exception派生??? class logic_error; //邏輯錯誤,在程序運行前可以檢測出來??? ??? //logic_error派生??? class domain_error; //違反了前置條件??? class invalid_argument; //指出函數的一個無效參數 ??? class length_error; //指出有一個超過類型size_t的最大可表現值長度的對象的企圖??? class out_of_range; //參數越界??? class bad_cast; //在運行時類型識別中有一個無效的dynamic_cast表達式??? class bad_typeid; //報告在表達試typeid(*p)中有一個空指針p??? ??? //exception派生??? class runtime_error; //運行時錯誤,僅在程序運行中檢測到??? ??? //runtime_error派生??? class range_error; //違反后置條件??? class overflow_error; //報告一個算術溢出??? class bad_alloc; //存儲分配錯誤}請注意觀察上述類的層次結構,可以看出,標準異常都派生自一個公共的基類exception。基類包含必要的多態性函數提供異常描述,可以被重載。下面是exception類的原型:class exception {public:??? exception() throw();??? exception(const exception& rhs) throw();??? exception& operator=(const exception& rhs) throw();??? virtual ~exception() throw();??? virtual const char *what() const throw();};其中的一個重要函數為what(),它返回一個表示異常的字符串指針。下面我們從exception類派生一個自己的類:#include <iostream>#include <exception>using namespace std;class myexception:public exception{public:??? myexception():exception("一個重載exception的例子")??? {??? }};int main(){??? try??? {??? ? throw myexception();?? ??? }??? catch (exception &r)? //捕獲異常??? {?????? cout << "捕獲到異常:" << r.what() << endl;??? }? ??? return 0;}程序運行,輸出:捕獲到異常:一個重載exception的例子一般的,我們直接以基類捕獲異常,例如,本例中使用了catch (exception &r)然后根據基類的多態性進行處理,這是因為基類中的what函數是虛函數。
圖2 assert的輸出圖2 assert的輸出失敗的斷言也會彈出如圖1所示的對話框,這是因為_assert()函數中也調用了abort()函數。一定要記住的是assert本質上是一個宏,而不是一個函數,因而不能把帶有副作用的表達式放入assert的“參數”中。1.3 errno?? errno在C程序中是一個全局變量,這個變量由C運行時庫函數設置,用戶程序需要在程序發生異常時檢測之。C運行庫中主要在math.h和stdio.h頭文件聲明的函數中使用了errno,前者用于檢測數學運算的合法性,后者用于檢測I/O操作中(主要是文件)的錯誤,例如:#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;}在此程序中,如果文件打開失敗(fopen返回NULL),證明發生了異常。我們讀取error可以獲知錯誤的原因,如果D盤根目錄下不存在“1.txt”文件,將輸出2,表示文件不存在;在文件存在并正確打開的情況下,將執行到else語句,輸出0,證明errno沒有被設置。Visual C++提供了兩種版本的C運行時庫。—個版本供單線程應用程序調用,另一個版本供多線程應用程序調用。多線程運行時庫與單線程運行時庫的一個重大差別就是對于類似errno的全局變量,每個線程單獨設置了一個。因此,對于多線程的程序,我們應該使用多線程C運行時庫,才能獲得正確的error值。另外,在使用errno之前,我們最好將其設置為0,即執行errno = 0的賦值語句。1.4 其它除了上述異常處理方式外,在C語言中還支持非局部跳轉(使用setjmp和longjmp)、信號(使用signal、raise)、返回錯誤值或回傳錯誤值給參數等方式進行一定能力的異常處理,但是其使用不如1.1~1.3節所介紹方式常用,我們不必過細研究。?從以上分析可知,C語言的異常處理是簡單而不全面的。與C++的異常處理比起來,C語言異常處理相形見絀,它就像娘胎里的雛嬰。?2.C++語言異常處理2.1 C++異常處理語法感謝C++語言的后期改造者們,他們在標準C++語言中專門集成了異常處理的相關語法(與之不同的是,所有的C 標準庫異常體系都需要運行庫的支持,它不是語言內核支持的)。當然,異常處理被加到程序設計語言中,也是程序語言發展和逐步完善的必然結果。我們看到,C++不是唯一集成異常處理的語言。C++的異常處理結構為:try{//可能引發異常的代碼}catch(type_1 e){// type_1類型異常處理}catch(type_2 e){// type_2類型異常處理}catch (...)//會捕獲所有未被捕獲的異常,必須最后出現{}而異常的拋出方式為使用throw(type e),try、catch和throw都是C++為處理異常而添加的關鍵字。看看這個例子:#include <stdio.h>//定義Point結構體(類)typedef struct tagPoint{? int x;? int y;} Point;//扔出int異常的函數static void f(int n){? throw 1;}//扔出Point異常的函數static void f(Point point){? Point p;? p.x = 0;? p.y = 0;? throw p;}int main(){? Point point;? point.x = 0;? point.y = 0;? try? {??? f(point); //拋出Point異常??? //f(1);?? //拋出int異常? }? catch (int e)? {??? printf("捕獲到int異常:%d\n", e);? }? catch (Point e)? {??? printf("捕獲到Point異常:(%d,%d)\n", e.x, e.y);? }? return 0;}函數f定義了兩個版本:f(int)和f(Point),分別拋出int和Point異常。當main函數的try{…}中調用f(point)時和f(1)時,分別輸出:捕獲到Point異常:(0,0)和捕獲到int異常:1在C++中,throw拋出異常的特點有:(1)可以拋出基本數據類型異常,如int和char等;(2)可以拋出復雜數據類型異常,如結構體(在C++中結構體也是類)和類;(3)C++的異常處理必須由調用者主動檢查。一旦拋出異常,而程序不捕獲的話,那么abort()函數就會被調用,彈出如圖1所示的對話框,程序被終止;(4)可以在函數頭后加throw([type-ID-list])給出異常規格,聲明其能拋出什么類型的異常。type-ID-list是一個可選項,其中包括了一個或多個類型的名字,它們之間以逗號分隔。如果函數沒有異常規格指定,則可以拋出任意類型的異常。2.2 標準異常下面給出了C++提供的一些標準異常:namespace std{??? //exception派生??? class logic_error; //邏輯錯誤,在程序運行前可以檢測出來??? ??? //logic_error派生??? class domain_error; //違反了前置條件??? class invalid_argument; //指出函數的一個無效參數 ??? class length_error; //指出有一個超過類型size_t的最大可表現值長度的對象的企圖??? class out_of_range; //參數越界??? class bad_cast; //在運行時類型識別中有一個無效的dynamic_cast表達式??? class bad_typeid; //報告在表達試typeid(*p)中有一個空指針p??? ??? //exception派生??? class runtime_error; //運行時錯誤,僅在程序運行中檢測到??? ??? //runtime_error派生??? class range_error; //違反后置條件??? class overflow_error; //報告一個算術溢出??? class bad_alloc; //存儲分配錯誤}請注意觀察上述類的層次結構,可以看出,標準異常都派生自一個公共的基類exception。基類包含必要的多態性函數提供異常描述,可以被重載。下面是exception類的原型:class exception {public:??? exception() throw();??? exception(const exception& rhs) throw();??? exception& operator=(const exception& rhs) throw();??? virtual ~exception() throw();??? virtual const char *what() const throw();};其中的一個重要函數為what(),它返回一個表示異常的字符串指針。下面我們從exception類派生一個自己的類:#include <iostream>#include <exception>using namespace std;class myexception:public exception{public:??? myexception():exception("一個重載exception的例子")??? {??? }};int main(){??? try??? {??? ? throw myexception();?? ??? }??? catch (exception &r)? //捕獲異常??? {?????? cout << "捕獲到異常:" << r.what() << endl;??? }? ??? return 0;}程序運行,輸出:捕獲到異常:一個重載exception的例子一般的,我們直接以基類捕獲異常,例如,本例中使用了catch (exception &r)然后根據基類的多態性進行處理,這是因為基類中的what函數是虛函數。
總結
以上是生活随笔為你收集整理的Visual C++中的异常处理浅析(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASP.Net中怎样获得存储过程传出的参
- 下一篇: SE《最终幻想 剧院节拍 FINAL B