日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Visual C++中的異常處理淺析

 9loong 2010-05-25
http://www./dev/vc/2009-05-24/a09110607.shtml

Visual C++提供了對(duì)C語(yǔ)言、C++語(yǔ)言及MFC的支持,因而其涉及到的異常(exception)處理也包含了這三種類型,即C語(yǔ)言、C++語(yǔ)言和MFC的異 常處理。除此之外,微軟對(duì)C和C++的異常處理進(jìn)行了擴(kuò)展,提出了結(jié)構(gòu)化異常處理(SEH)的概念,它支持C和C++(與之相比,MFC異常處理僅支持 C++)。

  一個(gè)典型的異常處理包含如下幾個(gè)步驟:

 ?。?)程序執(zhí)行時(shí)發(fā)生錯(cuò)誤;

  (2)以一個(gè) 異常對(duì)象(最簡(jiǎn)單的是一個(gè)整數(shù))記錄錯(cuò)誤的原因及相關(guān)信息;

  (3)程序檢測(cè)到這個(gè)錯(cuò)誤(讀取異常對(duì)象);

 ?。?) 程序決定如何處理錯(cuò)誤;

 ?。?)進(jìn)行錯(cuò)誤處理,并在此后恢復(fù)/終止程序的執(zhí)行。

  C、C++、MFC及SEH在這幾 個(gè)步驟中表現(xiàn)出了不同的特點(diǎn)。本文將對(duì)這四種異常處理進(jìn)行介紹,并對(duì)它們進(jìn)行對(duì)比分析。本文例程的調(diào)試平臺(tái)為Visual C++6.0,操作系統(tǒng)為Windows XP,所有程序均調(diào)試通過(guò)。

在進(jìn)入正式的講解之前,先說(shuō) 幾句廢話。許多的編程新手對(duì)異常處理視而不見(jiàn),程序里很少考慮異常情況。一部分人甚至根本就不考慮,以為程序總是能以正確的途徑運(yùn)行。譬如我們有的程序設(shè) 計(jì)者調(diào)用fopen打開(kāi)一個(gè)文件后,立馬就開(kāi)始進(jìn)行讀寫操作,根本就不考慮文件是否正常打開(kāi)了。這種習(xí)慣一定要改掉,縱使你再不愿意!這是軟件健壯性的需 要!異常處理不是浪費(fèi)時(shí)間!

  1.C語(yǔ)言異常處理

  1.1 異常終止

  標(biāo)準(zhǔn)C庫(kù)提 供了abort()和exit()兩個(gè)函數(shù),它們可以強(qiáng)行終止程序的運(yùn)行,其聲明處于<stdlib.h>頭文件中。這兩個(gè)函數(shù)本身不能檢測(cè) 異常,但在C程序發(fā)生異常后經(jīng)常使用這兩個(gè)函數(shù)進(jìn)行程序終止。下面的這個(gè)例子描述了exit()的行為:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
 exit(EXIT_SUCCESS);
 printf("程序不會(huì)執(zhí)行到這里\n");
return 0;
}

  在這個(gè)例子中,main函數(shù)一開(kāi)始就執(zhí)行了exit函數(shù) (此函數(shù)原型為void exit(int)),因此,程序不會(huì)輸出"程序不會(huì)執(zhí)行到這里"。程序中的exit(EXIT_SUCCESS)表示程序正常結(jié)束,與之對(duì)應(yīng)的 exit(EXIT_FAILURE)表示程序執(zhí)行錯(cuò)誤,只能強(qiáng)行終止。EXIT_SUCCESS、EXIT_FAILURE分別定義為0和1。

對(duì)于exit函數(shù),我們可以利用atexit函數(shù)為exit事件"掛接"另外的函數(shù),這種"掛接"有點(diǎn)類似Windows編程中的"鉤子" (Hook)。譬如:

#include <stdio.h>
#include <stdlib.h>
static void atExitFunc(void)
{
 printf("atexit掛接的函數(shù)\n");
}
int main(void)
{
 atexit(atExitFunc);
 exit(EXIT_SUCCESS);
printf("程序不會(huì)執(zhí)行到這里\n");
 return 0;
}

程序輸出"atexit掛接的函數(shù)"后即終止。來(lái)看下面的程序,我們不調(diào)用exit函數(shù),看看atexit掛接的函數(shù)會(huì)否執(zhí)行:

#include <stdio.h>
#include <stdlib.h>
static void atExitFunc(void)
{
 printf("atexit掛接的函數(shù)\n");
}
int main(void)
{
 atexit(atExitFunc);
 //exit(EXIT_SUCCESS);
printf("不調(diào)用exit函數(shù)\n");
 return 0;
}

程序輸出:

  不調(diào)用exit函數(shù)

  atexit掛接的函數(shù)

  這說(shuō)明,即便是我們不調(diào)用exit 函數(shù),當(dāng)程序本身退出時(shí),atexit掛接的函數(shù)仍然會(huì)被執(zhí)行。

  atexit可以被多次執(zhí)行,并掛接多個(gè)函數(shù),這些函數(shù)的執(zhí)行順序?yàn)? 后掛接的先執(zhí)行,例如:

#include <stdio.h>
#include <stdlib.h>

static void atExitFunc1(void)
{
 printf("atexit掛接的函數(shù)1\n");
}

static void atExitFunc2(void)
{
 printf("atexit掛接的函數(shù)2\n");
}

static void atExitFunc3(void)
{
 printf("atexit掛接的函數(shù)3\n");
}

int main(void)
{
 atexit(atExitFunc1);
 atexit(atExitFunc2);
atexit(atExitFunc3);
 return 0;
}

  輸 出的結(jié)果是:

   atexit掛接的函數(shù)3
   atexit掛接的函數(shù)2
   atexit掛接的函數(shù)1

在Visual C++中,如果以abort函數(shù)(此函數(shù)不帶參數(shù),原型為void abort(void))終止程序,則會(huì)在debug模式運(yùn)行時(shí)彈出如圖1所示的對(duì)話框。


圖1 以abort函數(shù)終止程序

  1.2 斷言(assert)

assert宏在C語(yǔ)言程序的調(diào)試中發(fā)揮著重要的作用,它用于檢測(cè)不會(huì)發(fā)生的情況,表明一旦發(fā)生了這樣的情況,程序就實(shí)際上執(zhí)行錯(cuò)誤了,例如 strcpy函數(shù):

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) ),它的意思是源和目的字符串的地址都不能為空,一旦為空,程序?qū)嶋H上就執(zhí)行錯(cuò)誤了,會(huì)引發(fā)一個(gè)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宏實(shí)際上什么 都不做;而在debug模式下,實(shí)際上是對(duì)_assert()函數(shù)的調(diào)用,此函數(shù)將輸出發(fā)生錯(cuò)誤的文件名、代碼行、條件表達(dá)式。例如下列程序:

#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庫(kù)中的strcpy重名,將其改為myStrcpy。程序的輸出如圖2:


圖2 assert的輸出

  失敗的斷言也會(huì)彈出如圖1所示的對(duì)話框,這是因?yàn)? _assert()函數(shù)中也調(diào)用了abort()函數(shù)。

  一定要記住的是assert本質(zhì)上是一個(gè)宏,而不是一個(gè)函數(shù),因而不能把帶有 副作用的表達(dá)式放入assert的"參數(shù)"中。

  1.3 errno

  errno在C程序中是一個(gè)全局變量,這個(gè)變 量由C運(yùn)行時(shí)庫(kù)函數(shù)設(shè)置,用戶程序需要在程序發(fā)生異常時(shí)檢測(cè)之。C運(yùn)行庫(kù)中主要在math.h和stdio.h頭文件聲明的函數(shù)中使用了errno,前者 用于檢測(cè)數(shù)學(xué)運(yùn)算的合法性,后者用于檢測(cè)I/O操作中(主要是文件)的錯(cuò)誤,例如:

#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;
}

在此程序中,如果文件打開(kāi)失敗(fopen返回NULL),證明發(fā)生了異常。我們讀取error可以獲知錯(cuò)誤的原因,如果D盤根目錄下不存 在"1.txt"文件,將輸出2,表示文件不存在;在文件存在并正確打開(kāi)的情況下,將執(zhí)行到else語(yǔ)句,輸出0,證明errno沒(méi)有被設(shè)置。

Visual C++提供了兩種版本的C運(yùn)行時(shí)庫(kù)。-個(gè)版本供單線程應(yīng)用程序調(diào)用,另一個(gè)版本供多線程應(yīng)用程序調(diào)用。多線程運(yùn)行時(shí)庫(kù)與單線程運(yùn)行時(shí)庫(kù)的一個(gè)重大差別就是 對(duì)于類似errno的全局變量,每個(gè)線程單獨(dú)設(shè)置了一個(gè)。因此,對(duì)于多線程的程序,我們應(yīng)該使用多線程C運(yùn)行時(shí)庫(kù),才能獲得正確的error值。

另外,在使用errno之前,我們最好將其設(shè)置為0,即執(zhí)行errno = 0的賦值語(yǔ)句。

  1.4 其它

  除了 上述異常處理方式外,在C語(yǔ)言中還支持非局部跳轉(zhuǎn)(使用setjmp和longjmp)、信號(hào)(使用signal、raise)、返回錯(cuò)誤值或回傳錯(cuò)誤值 給參數(shù)等方式進(jìn)行一定能力的異常處理,但是其使用不如1.1~1.3節(jié)所介紹方式常用,我們不必過(guò)細(xì)研究。

  從以上分析可知,C語(yǔ)言的 異常處理是簡(jiǎn)單而不全面的。與C++的異常處理比起來(lái),C語(yǔ)言異常處理相形見(jiàn)絀,它就像娘胎里的雛嬰。

2.C++語(yǔ)言異常處理

  2.1 C++異常處理語(yǔ)法

感謝C++語(yǔ)言的后期改造者們,他們?cè)跇?biāo)準(zhǔn)C++語(yǔ)言中專門集成了異常處理的相關(guān)語(yǔ)法(與之不同的是,所有的C 標(biāo)準(zhǔn)庫(kù)異常體系都需要運(yùn)行庫(kù)的支持,它不是語(yǔ)言內(nèi)核支持的)。當(dāng)然,異常處理被加到程序設(shè)計(jì)語(yǔ)言中,也是程序語(yǔ)言發(fā)展和逐步完善的必然結(jié)果。我們看 到,C++不是唯一集成異常處理的語(yǔ)言。

  C++的異常處理結(jié)構(gòu)為:

try
{
//可能引發(fā)異常的代碼
}
catch(type_1 e)
{
// type_1類型異常處理
}
catch(type_2 e)
{
// type_2類型異常處理
}
catch (...)//會(huì)捕獲所有未被捕獲的異常,必須最后出現(xiàn)
{
}

  而異常 的拋出方式為使用throw(type e),try、catch和throw都是C++為處理異常而添加的關(guān)鍵字。看看這個(gè)例子:

#include <stdio.h>
//定義Point結(jié)構(gòu)體(類)
typedef struct tagPoint
{
 int x;
 int y;
} Point;
//扔出int異常的函數(shù)
static void f(int n)
{
 throw 1;
}

//扔出Point異常的函數(shù)
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;
}

  函數(shù)f定義了 兩個(gè)版本:f(int)和f(Point),分別拋出int和Point異常。當(dāng)main函數(shù)的try{…}中調(diào)用f(point)時(shí)和f(1)時(shí),分別 輸出:

  捕獲到Point異常:(0,0)

  和

  捕獲到int異常:1

  在 C++中,throw拋出異常的特點(diǎn)有:

 ?。?)可以拋出基本數(shù)據(jù)類型異常,如int和char等;

  (2)可以拋 出復(fù)雜數(shù)據(jù)類型異常,如結(jié)構(gòu)體(在C++中結(jié)構(gòu)體也是類)和類;

 ?。?)C++的異常處理必須由調(diào)用者主動(dòng)檢查。一旦拋出異常,而程序 不捕獲的話,那么abort()函數(shù)就會(huì)被調(diào)用,彈出如圖1所示的對(duì)話框,程序被終止;

 ?。?)可以在函數(shù)頭后加 throw([type-ID-list])給出異常規(guī)格,聲明其能拋出什么類型的異常。type-ID-list是一個(gè)可選項(xiàng),其中包括了一個(gè)或多個(gè)類 型的名字,它們之間以逗號(hào)分隔。如果函數(shù)沒(méi)有異常規(guī)格指定,則可以拋出任意類型的異常。

  2.2 標(biāo)準(zhǔn)異常

  下面給 出了C++提供的一些標(biāo)準(zhǔn)異常:

namespace std
{
 //exception派生
 class logic_error; //邏輯錯(cuò)誤,在程序運(yùn)行前可以檢測(cè)出來(lái)

 //logic_error派生
 class domain_error; //違反了前置條件
 class invalid_argument; //指出函數(shù)的一個(gè)無(wú)效參數(shù)
 class length_error; //指出有一個(gè)超過(guò)類型size_t的最大可表現(xiàn)值長(zhǎng)度的對(duì)象的企圖
 class out_of_range; //參數(shù)越界
 class bad_cast; //在運(yùn)行時(shí)類型識(shí)別中有一個(gè)無(wú)效的dynamic_cast表達(dá)式
 class bad_typeid; //報(bào)告在表達(dá)試typeid(*p)中有一個(gè)空指針p

 //exception派生
 class runtime_error; //運(yùn)行時(shí)錯(cuò)誤,僅在程序運(yùn)行中檢測(cè)到

 //runtime_error派生
 class range_error; //違反后置條件
 class overflow_error; //報(bào)告一個(gè)算術(shù)溢出
 class bad_alloc; //存儲(chǔ)分配錯(cuò)誤
}

  請(qǐng)注意觀察上述類的層次結(jié)構(gòu),可以 看出,標(biāo)準(zhǔn)異常都派生自一個(gè)公共的基類exception?;惏匾亩鄳B(tài)性函數(shù)提供異常描述,可以被重載。下面是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();
};

其中的一個(gè)重要函數(shù)為what(),它返回一個(gè)表示異常的字符串指針。下面我們從exception類派生一個(gè)自己的類:

#include <iostream>
#include <exception>
using namespace std;

class myexception:public exception
{
public:
 myexception():exception("一個(gè)重載exception的例子")
 {}
};

int main()
{
 try
 {
  throw myexception();
 }
 catch (exception &r) //捕獲異常
 {
  cout << "捕獲到異常:" << r.what() << endl;
 }
 return 0;
}

程序運(yùn)行,輸出:

  捕獲到異常:一個(gè)重載exception的例子

  一般的,我們直接以基類捕獲異常,例如,本例 中使用了

catch (exception &r)

  然后根據(jù)基類的多態(tài)性進(jìn)行 處理,這是因?yàn)榛愔械膚hat函數(shù)是虛函數(shù)。

2.3異常處理函數(shù)

  在標(biāo)準(zhǔn)C++中,還定義了數(shù)個(gè)異常處理的相關(guān)函數(shù)和類型(包含在頭 文件<exception>中):

namespace std
{
 //EH類型
 class bad_exception;
 class exception;

 typedef void (*terminate_handler)();
 typedef void (*unexpected_handler)();

 // 函數(shù)
 terminate_handler set_terminate(terminate_handler) throw();
 unexpected_handler set_unexpected(unexpected_handler) throw();

 void terminate();
void unexpected();

 bool uncaught_exception();
}

其中的terminate相關(guān)函數(shù)與未被捕獲的異常有關(guān),如果一種異常沒(méi)有被指定catch模塊,則將導(dǎo)致terminate()函數(shù)被調(diào) 用,terminate()函數(shù)中會(huì)調(diào)用ahort()函數(shù)來(lái)終止程序??梢酝ㄟ^(guò)set_terminate(terminate_handler)函數(shù) 為terminate()專門指定要調(diào)用的函數(shù),例如:

#include <cstdio>
#include <exception>
using namespace std;
//定義Point結(jié)構(gòu)體(類)
typedef struct tagPoint
{
int x;
 int y;
} Point;
//扔出Point異常的函數(shù)
static void f()
{
Point p;
 p.x = 0;
 p.y = 0;
 throw p;
}
//set_terminate將 指定的函數(shù)
void terminateFunc()
{
 printf("set_terminate指定的函數(shù)\n");
}

int main()
{
 set_terminate(terminateFunc);
 try
 {
  f(); //拋出Point異常
 }
 catch (int) //捕獲int異常
 {
  printf("捕獲到int異 常");
 }
 //Point將不能被捕獲到,引發(fā)terminateFunc函數(shù)被執(zhí)行

 return 0;
}

這個(gè)程序?qū)⒃诳刂婆_(tái)上輸出 "set_terminate指定的函數(shù)" 字符串,因?yàn)镻oint類型的異常沒(méi)有被捕獲到。當(dāng)然,它也會(huì)彈出圖1所示對(duì)話框(因?yàn)檎{(diào)用了abort()函數(shù))。

  上述給出的僅僅 是一個(gè)set_terminate指定函數(shù)的例子。在實(shí)際工程中,往往使用set_terminate指定的函數(shù)進(jìn)行一些清除性的工作,其后再調(diào)用 exit(int)函數(shù)終止程序。這樣,abort()函數(shù)就不會(huì)被調(diào)用了,也不會(huì)輸出圖1所示對(duì)話框。

  關(guān)于標(biāo)準(zhǔn)C++的異常處理, 還包含一些比較復(fù)雜的技巧和內(nèi)容,我們可以查閱《more effective C++》的條款9~條款15。

3.MFC異常處理

  MFC中異常處理的語(yǔ)法和語(yǔ)義構(gòu)建在標(biāo)準(zhǔn)C++異常 處理語(yǔ)法和語(yǔ)義的基礎(chǔ)之上,其解決方案為:

  MFC異常處理 = MFC 異常處理類 + 宏

  3.1宏

MFC定義了TRY、CATCH(及AND_CATCH、END_CATCH)和THROW(及THROW_LAST)等用于異常處理的宏,其本質(zhì)上也 是標(biāo)準(zhǔn)C++的try、catch和throw的進(jìn)一步強(qiáng)化,由這些宏的定義可知:

#ifndef _AFX_OLD_EXCEPTIONS

#define TRY { AFX_EXCEPTION_LINK _afxExceptionLink; try {

#define CATCH(class, e) } catch (class* e) \
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); \
_afxExceptionLink.m_pException = e;

#define AND_CATCH(class, e) } catch (class* e) \
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); \
_afxExceptionLink.m_pException = e;

#define END_CATCH } }

#define THROW(e) throw e
#define THROW_LAST() (AfxThrowLastCleanup(), throw)

// Advanced macros for smaller code
#define CATCH_ALL(e) } catch (CException* e) \
{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \
_afxExceptionLink.m_pException = e;

#define AND_CATCH_ALL(e) } catch (CException* e) \
{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \
_afxExceptionLink.m_pException = e;

#define END_CATCH_ALL } } }

#define END_TRY } catch (CException* e) \
{ ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \
_afxExceptionLink.m_pException = e; } }

  這些宏在使用語(yǔ)法上,有如下特點(diǎn):

  (1)用 TRY 塊包含可能產(chǎn)生異常的代碼;

 ?。?)用CATCH塊檢測(cè)并處理異常。要注意的是,CATCH塊捕獲到的不是異常對(duì)象,而是指向 異常對(duì)象的指針。此外,MFC靠動(dòng)態(tài)類型來(lái)辨別異常對(duì)象;

 ?。?)可以在一個(gè)TRY 塊上捆綁多個(gè)異常處理捕獲塊,第一次捕獲使用宏CATCH,以后的使用AND_CATCH,而END_CATCH則用來(lái)結(jié)束異常捕獲隊(duì)列;

(4)在異常處理程序內(nèi)部,可以用THROW_LAST 再次拋出最近一次捕獲的異常。

  3.2 MFC 異常處理類

MFC較好地將異常封裝到CException類及其派生類中,自成體系,下表給出了MFC 提供的預(yù)定義異常:

異常類 含義
CMemoryException 內(nèi)存不足
CFileException 文件異常
CArchiveException 存檔/序列化異常
CNotSupportedException 響應(yīng)對(duì)不支持服務(wù)的請(qǐng)求
CResourceException Windows 資源分配異常
CDaoException 數(shù)據(jù)庫(kù)異常(DAO 類)
CDBException 數(shù)據(jù)庫(kù)異常(ODBC 類)
COleException OLE 異常
COleDispatchException 調(diào)度(自動(dòng)化)異常
CUserException 用消息框警告用戶然后引發(fā)一般 CException 的異常

  標(biāo)準(zhǔn)C++的異 常處理可以處理任意類型的異常,而3.1節(jié)的MFC 宏則只能處理CException 的派生類型,下面我們看一個(gè)CFileException的使用例子:

#include <iostream.h>
#include "afxwin.h"

int main()
{
TRY
 {
  CFile f( "d:\\1.txt", CFile::modeWrite );
 }
CATCH( CFileException, e )
 {
  if( e->m_cause == CFileException::fileNotFound )
   cout << "ERROR: File not found\n" << endl;
 }
 END_CATCH
}

在這個(gè)程序中,如果D盤根目錄下不存在"1.TXT"這個(gè)文件,將拋出CFileException異常,而且錯(cuò)誤原因成員變量m_cause被設(shè)置為 fileNotFound,我們以CATCH( CFileException, e )就可以捕獲到。錯(cuò)誤原因被定義為CFileException中的枚舉(enum),如下:

enum {
 none,
 generic,
 fileNotFound,
 badPath,
tooManyOpenFiles,
 accessDenied,
 invalidFile,
removeCurrentDir,
 directoryFull,
 badSeek,
 hardIO,
sharingViolation,
 lockViolation,
 diskFull,
 endOfFile
};

我們?cè)谑褂肕FC相關(guān)類時(shí),MFC會(huì)自動(dòng)拋出異常,當(dāng)然我們也可以自行在程序中利用AfxThrowXXXException()拋出各種類型的異常, 其中的XXX與前文的MFC異常類表對(duì)應(yīng)。我們看AfxThrowFileException的例子:

#include <iostream.h>
#include "afxwin.h"

int main()
{
TRY
 {
  AfxThrowFileException(CFileException::fileNotFound);
}
 CATCH( CFileException, e )
 {
  if( e->m_cause == CFileException::fileNotFound )
   cout << "ERROR: File not found\n" << endl;
 }
 END_CATCH
}

在此程序中,我們?cè)赥RY塊自行利用MFC提供的全局函數(shù)AfxThrowFileException拋出了CFileException異常,其后在 CATCH塊抓住了這個(gè)異常。

  MFC建議不再使用TRY、CATCH和THROW宏,而是直接使用標(biāo)準(zhǔn)C++的方式。

4.結(jié)構(gòu)化異常處理

  結(jié)構(gòu)化異常處理(Structured Exception Handling,簡(jiǎn)稱SEH)是微軟針對(duì)Windows程序異常處理進(jìn)行的擴(kuò)展,在Visual C++中,它同時(shí)支持C和C++語(yǔ)言。SEH不宜與標(biāo)準(zhǔn)C++異常處理和MFC異常處理混用,對(duì)于C++程序,微軟建議使用標(biāo)準(zhǔn)C++的異常處理。

為了支持SEH,Visual C++中定義了四個(gè)關(guān)鍵字(由于這些關(guān)鍵字是非標(biāo)準(zhǔn)關(guān)鍵字,其它編譯器不一定支持),用以擴(kuò)展C 和C++語(yǔ)言:

(1)__except

 ?。?)__finally

 ?。?)__leave

 ?。?)__try

其基本語(yǔ)法為:

__try
{
...//可能導(dǎo)致異常的被監(jiān)控代碼塊
}
__except(filter-expression)
{
...// 異常處理函數(shù)
}

  或:

__try
{
...
}
__finally
{
...//終止
}

其執(zhí)行的步驟如下:

  (1)__try塊被執(zhí)行;

 ?。?)如果__try塊沒(méi)有出現(xiàn)異常,則執(zhí)行到 __except塊之后;否則,執(zhí)行到__except塊,根據(jù)filter-expression的值決定異常處理方法:

  a. filter-expression的值為EXCEPTION_CONTINUE_EXECUTION (-1)

  恢復(fù)異常,從發(fā)生異 常處下面開(kāi)始執(zhí)行,異常處理函數(shù)本身不被執(zhí)行;

  b. filter-expression的值為EXCEPTION_CONTINUE_SEARCH (0)

  異常不被識(shí)別,拒絕捕獲異 常,繼續(xù)搜索下一個(gè)異常處理函數(shù);

  c. filter-expression的值為EXCEPTION_EXECUTE_HANDLER (1)

  異常被識(shí)別,終止異常,從異 常發(fā)生處開(kāi)始退棧,一路上遇到的終止函數(shù)都被執(zhí)行。

  看看這個(gè)例子:

//例4-1
#include "stdio.h"

void main()
{
 int* p = NULL; // 定義一個(gè)空指針
 puts("SEH begin");
 __try
 {
  puts("in try");
  __try
  {
   puts("in try");
   *p = 0; // 引發(fā)一個(gè)內(nèi)存訪問(wèn)異常
  }
  __finally
  {
   puts("in finally");
  }
}
 __except(puts("in filter"), 1)
 {
  puts("in except");
 }
puts("SEH end");
}

  程序的輸出為:

SEH begin
in try //執(zhí)行__try塊
in try //執(zhí)行嵌入的__try塊
in filter //執(zhí)行filter-expression,返回EXCEPTION_EXECUTE_HANDLER
in finally //展開(kāi)嵌入的__finally
in except //執(zhí)行對(duì)應(yīng)的__except塊
SEH end //處理完畢

如果我們把__except(puts("in filter"), 1)改為_(kāi)_except(puts("in filter"), 0),程序的輸出將變?yōu)椋?br>
SEH begin
in try //執(zhí)行__try塊
in try //執(zhí)行嵌入的__try塊
in filter //執(zhí)行filter-expression,返回EXCEPTION_CONTINUE_SEARCH
in finally //展開(kāi)嵌入的__finally

  程序的執(zhí)行也告崩潰,彈出如圖3所示的對(duì)話框。


圖3 不能被正確執(zhí)行的SEH

  要想這個(gè)程序能正確地執(zhí)行,我們可以在第一個(gè) __try塊的外面再套一個(gè)__try塊和一個(gè)接收f(shuō)ilter-expression返回值為EXCEPTION_EXECUTE_HANDLER的 __except塊,程序改為:

//例4-2
#include "stdio.h"

void main()
{
 int* p = NULL; // 定義一個(gè)空指針
 puts("SEH begin");
 __try
 {
  __try
{
   puts("in try");
   __try
   {
    puts("in try");
*p = 0; // 引發(fā)一個(gè)內(nèi)存訪問(wèn)異常
   }
   __finally
   {
    puts("in finally");
   }
  }
  __except(puts("in filter"), 0)
  {
puts("in except");
  }
 }
 __except(puts("in filter"), 1)
{
  puts("in except");
 }
 puts("SEH end");
}

程序輸出:

SEH begin
in try //執(zhí)行__try塊
in try //執(zhí)行嵌入的__try塊
in filter1 //執(zhí)行filter-expression,返回EXCEPTION_CONTINUE_SEARCH
in filter2 //執(zhí)行filter-expression,返回EXCEPTION_EXECUTE_HANDLER
in finally //展開(kāi)嵌入的__finally
in except2 //執(zhí)行對(duì)應(yīng)的__except塊
SEH end //處理完畢

由此可以看出,因?yàn)榈谝粋€(gè)__except的filter-expression返回EXCEPTION_CONTINUE_SEARCH 的原因,"in except1"沒(méi)有被輸出。程序之所以沒(méi)有崩潰,是因?yàn)樽罱K碰到了接收EXCEPTION_EXECUTE_HANDLER的第2個(gè)__except。

SEH使用復(fù)雜的地方在于較難控制異常處理的流動(dòng)方向,弄不好程序就"掛"了。如果把例4-1中的__except(puts("in filter"), 1)改為_(kāi)_except(puts("in filter"), -1),程序會(huì)進(jìn)入一個(gè)死循環(huán),輸出:

SEH begin
in try //執(zhí)行__try塊
in try //執(zhí)行嵌入的__try塊
in filter //執(zhí)行filter-expression,返回EXCEPTION_CONTINUE_EXECUTION
in filter
in filter
in filter
in filter
…//瘋狂輸出"in filter"

最后瘋狂地輸出"in filter",我們把斷點(diǎn)設(shè)置在__except(puts("in filter"), -1)語(yǔ)句之前,按F5會(huì)不斷進(jìn)入此斷點(diǎn)。

  5.各種異常處理的對(duì)比

  下表給出了從各個(gè)方面對(duì)這本文所給出的 Visual C++所支持的四種異常處理進(jìn)行的對(duì)比:

異常處理 支持語(yǔ)言 是否標(biāo)準(zhǔn) 復(fù)雜度 推薦使用
C異常處理 C語(yǔ)言 標(biāo)準(zhǔn)C 簡(jiǎn)單 推薦
C++異常處理 C++語(yǔ)言 標(biāo)準(zhǔn)C++ 較簡(jiǎn)單 推薦
MFC異常處理 C++語(yǔ)言 僅針對(duì)MFC程序 較簡(jiǎn)單 不推薦
SEH異常處理 C和C++語(yǔ)言 僅針對(duì)Microsoft編譯環(huán)境 較復(fù)雜 不推薦

  本文所講解的僅僅是Visual C++異常處理的初步知識(shí),對(duì)于更深入的內(nèi)容,還需要我們?cè)诓粩嗟木幊踢^(guò)程中去領(lǐng)悟和學(xué)習(xí)。

  在程序設(shè)計(jì)過(guò)程中,我們不能嫌異常處理" 麻煩",對(duì)可能的錯(cuò)誤視而不見(jiàn)、不加考慮。因?yàn)楸苊饬水惓L幚淼?麻煩",將會(huì)給我們的程序帶來(lái)更大的"麻煩"。而程序中包含必要的異常處理,也是對(duì)一位 優(yōu)秀程序員的基本要求。

(#)

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多