下載本文檔
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
c與C++中的異常處理[2006-7-116:57:01][來源:百家電腦學(xué)院]C對它的支持(前言略)異常分類基于Dr.GUI的建議,我把我的第一個(gè)專欄投入到“程序異?!钡南盗猩?。我認(rèn)識到,"exceptionw這個(gè)術(shù)語有些不明確并和上下文相關(guān),尤其是C++標(biāo)準(zhǔn)異常(C++standardexceptions)和Microsoft的結(jié)構(gòu)化異常(structuredexceptionhandling)。不幸的的是,"異?!暴`詞太常見了,隨時(shí)出現(xiàn)在語言的標(biāo)準(zhǔn)和常見的編程文獻(xiàn)中。因?yàn)椴幌雱?chuàng)造?個(gè)新名詞,所以我將盡力在此系列的各部分中明確我對’‘異?!钡挠梅?。Part1概述通常意義上的異常的性質(zhì),和標(biāo)準(zhǔn)C庫提供的處理它們的方法。Part2縱覽Microsoft對這些標(biāo)準(zhǔn)C庫方法的擴(kuò)展:專門的宏和結(jié)構(gòu)化異常處理Part3及其余將致力于標(biāo)準(zhǔn)C++異常處理體系。(C語言使用者可能在Part2后放棄,但我鼓勵你堅(jiān)持到底;我所提出的許多點(diǎn)子同樣適用于C,雖然不是很直接。)本質(zhì)上看,程序異常是指出現(xiàn)了一些很少發(fā)生的或出乎意料的狀態(tài),通常顯示了一個(gè)程序錯(cuò)誤或要求一個(gè)必須提供的回應(yīng)。不能滿足這個(gè)回應(yīng)經(jīng)常造成程序功能削弱或死亡,有時(shí)導(dǎo)致整個(gè)系統(tǒng)和它ー起down掉。不幸的是,試圖使用傳統(tǒng)的防護(hù)方法來編制健壯的代碼經(jīng)常只是將一個(gè)問題(意外崩潰)換成了另外一個(gè)問題(更混亂的設(shè)計(jì)和代碼)。太多的程序員認(rèn)為這個(gè)交換抵不上程序意外崩潰時(shí)造成的煩惱,于是選擇了生活在危險(xiǎn)之中。認(rèn)識到這一點(diǎn)后,C++標(biāo)準(zhǔn)增加了一個(gè)優(yōu)雅并且基本上不可見的“異常體系”到語言中;就這樣,這個(gè)方法產(chǎn)生了。如同我們在Part4的開始部分將要看到的,這個(gè)方法大部分情況下很成功,但在很微妙的情況下可能失敗。異常的生命階段在這個(gè)系列里,我將展示C和C++處理異常體系運(yùn)行于異常整個(gè)生命期的每ー階段時(shí)的不同之處:階段1:ー個(gè)軟件錯(cuò)誤發(fā)生。這個(gè)錯(cuò)誤也許產(chǎn)生于ー個(gè)被底層驅(qū)動或內(nèi)核映射為軟件錯(cuò)誤的硬件響應(yīng)事件(如被〇除)。階段2;錯(cuò)誤的原因和性質(zhì)被ー個(gè)異常對象攜帶。這個(gè)對象的類型可以簡單的整數(shù)值到繁雜的C++類對象。階段3:你的程序必須檢測這個(gè)異常對象:或者輪詢它的存在,或者由其主動上報(bào)。階段4:檢測代碼必須決定如何處理異常。典型的方法分成三類。a忽略異常對象,并期望別人處理它。b在這個(gè)對象上干些什么,并還允許別人再繼續(xù)處理它。c獲得異常的全部所有權(quán)。階段5:既然異常一經(jīng)處理了,程序通常恢復(fù)并繼續(xù)執(zhí)行?;謴?fù)分成兩種:a恢復(fù)異常,從異常發(fā)生處繼續(xù)執(zhí)行。b終止異常,從異常被處理處繼續(xù)執(zhí)行當(dāng)在程序外面(由運(yùn)行期庫或操作系統(tǒng))終止異常時(shí),恢復(fù)經(jīng)常是不可能的,程序?qū)惓=Y(jié)束。我故意忽略了硬件錯(cuò)誤事件,因?yàn)樗鼈兺耆堑讓悠脚_范圍內(nèi)的事。取而代之,我假定ー些軟件上的可檢測錯(cuò)誤已經(jīng)發(fā)生,并產(chǎn)生了一個(gè)處于第一階段的軟件異常對象。C標(biāo)準(zhǔn)庫異常處理體系C標(biāo)準(zhǔn)庫提供了幾個(gè)方法來處理異常。它們也全部在標(biāo)準(zhǔn)C++中有效,只是相關(guān)的頭文件名字變了:老的C標(biāo)準(zhǔn)頭文件<name.h>映射到了新的C++標(biāo)準(zhǔn)頭文件<cname>。(頭文件名的前綴“C”是個(gè)助記符,暗示著這些全是C庫頭文件。)雖然基于向后兼容性,老的C頭文件也被C++保留,但我建議你盡可能使用新的頭文件。對于絕大部分實(shí)際使用而言,最大的變化是在新的頭文件中,申明的函數(shù)被包含在命名空間std內(nèi)。舉個(gè)例子,C語言使用#include<stdio.h>FILE*f=fopen(,'blamey.txt,\nrn);在C++中被改成#include<cstdio>std::FILE*f=std::fopen(',blarney.txt,',^r*');或更c(diǎn)風(fēng)格的#include<cstdio>usingnamespacestd;FILE*f=fopen("blarney.txt”,T);不幸的是,Microsoft的VisualC++沒有將這些新的頭文件包含在命名空間std中,雖然這是C++標(biāo)準(zhǔn)所要求的(subclauseD.5)o除非VisualC++在這些頭文件中已經(jīng)正確地支持了std,我將一直在我的專欄中使用老式的C風(fēng)格命名。(象Microsoft這樣的運(yùn)行庫賣主這么做是合理的,正確地實(shí)現(xiàn)這些C程序庫的頭文件極可能要求維護(hù)和測試兩份完全不同的底層代碼,這是不可能受歡迎的也不值得多花カ氣的工作。)無條件終止僅次于徹底忽略ー個(gè)異常,大概最容易的異常處理方法是程序自我毀滅。有時(shí),最懶的方法事實(shí)上是最正確的。在你開始嘲笑以前,應(yīng)該認(rèn)識到,ー些異常表示的狀況是如此嚴(yán)重:以致于怎么也不可能合理恢復(fù)的。也許最好的例子就是malloc時(shí)返回NULL?如果空閑堆管理程序不能提供可用的連續(xù)空間,你程序的健壯性將嚴(yán)重受損,并且恢変的可能性是渺茫的。C庫頭文件vstdlib.h>提供了兩個(gè)終止程序的函數(shù):abort。和exit。。這兩個(gè)函數(shù)運(yùn)行于異常生命期的4和5。它們都不會返回到其調(diào)用者中,并都導(dǎo)致程序結(jié)束。這樣,它們就是結(jié)束異常處理的最后ー步。雖然兩個(gè)函數(shù)在概念上是相聯(lián)系的,但它們的效果不同:abort。:程序異常結(jié)束。默認(rèn)情況下,調(diào)用abort。導(dǎo)致運(yùn)行期診斷和程序自毀。它可能會也可能不會刷新緩沖區(qū)、關(guān)閉被打開的文件及刪除臨時(shí)文件,這依賴于你的編譯器的具體實(shí)現(xiàn)。exit。:文明地結(jié)束程序。除了關(guān)閉文件和給運(yùn)行環(huán)境返回一個(gè)狀態(tài)碼外,exit。還調(diào)用了你掛接的atexit。處理程序。一般調(diào)用abort。處理災(zāi)難性的程序故障。因?yàn)閍bort。的默認(rèn)行為是立即終止程序,你就必須負(fù)責(zé)在調(diào)用abort。前存儲幣:要數(shù)據(jù)。(當(dāng)我們談?wù)摰絭signal.h>時(shí),你可以使得abort。自動調(diào)用cleanup代碼。)相反,exit。執(zhí)行了掛接在atexit。上的自定義cleanup代碼。這些代碼被按照其掛接的反序執(zhí)行,你可以把它們當(dāng)作虛擬析構(gòu)器。通過必要的cleanup代碼,你可以安全地終止程序而沒有留下尾巴。例如:#include<stdio.h>#include<stdlib.h>staticvoidatexit_handler_l(void)(printf(Mwithinzatexit_handler_lAn");)staticvoidatexit_handler_2(void)(printf("withinxatexit_handler_2AnM);}intmain(void)(atexit(atexit_handler_l);atexit(atexit_handler_2);exit(EXIT_SUCCESS);printf("thislineshouldneverappear\n");return0;}/*Whenrunyieldswithinzatexit_handler_2'withinzatexit_handler_lzandreturnsasuccesscodetocallingenvironment.號(注意,即使是程序從main。正常返回而沒有明確調(diào)用exit。,所掛接的atexit()代碼仍然會被調(diào)用。)無論abort。還是exit。都不會返回到它的調(diào)用者中,且都將導(dǎo)致程序結(jié)束。在這個(gè)意義上來說,它們都表現(xiàn)為終止異常的最后ー步。有條件地終止abort。和exit。讓你無條件終止程序。你還可以有條件地終止程序。其實(shí)現(xiàn)體系是每個(gè)程序員所喜愛的診斷工具:斷言,定義于vassert.h>。這個(gè)宏的典型實(shí)現(xiàn)如下所示:#ifdefinedNDEBUG#defineassert(condition)((void)0)#else#defineassert(condition)\_assert((condition),#condition,_FILE_,_LINE)#endif如定義體所示,當(dāng)宏NDEBUG被定義時(shí)斷言是無行為的,這暗示了它只對調(diào)試版本有效。于是,斷言條件從不在非調(diào)試版本中被求值,這會造成同樣的代碼在調(diào)試和非調(diào)試版本間有奇妙的差異。/*debugversion*/#undefNDEBUG#include<assert.h>#include<stdio.h>intmain(void)inti=0;assert(++i!=0);printf(Miis%d\n*',i);return0;)/*Whenrunyieldsiis1*/現(xiàn)在,通過定義NDEBUG?從debug版變到release版:/*releaseversion*/#defingNDEBUG#include<assert.h>#include<stdio.h>intmain(void)(inti=0;assert(++i!=0);printf(Miis%d\n",i);return0;}/*WhenrunyieldsiisO*/要避免這個(gè)差異,必須確保斷言表達(dá)式的求值不會包含有影響的副作用。在僅供調(diào)試版使用的定義體中,斷言變成呼叫一assert。函數(shù)。我起了這個(gè)名字,而你所用的運(yùn)行庫的實(shí)現(xiàn)可以調(diào)用任何它想調(diào)用的內(nèi)部函數(shù)。無論它叫什么,這個(gè)函數(shù)通常有以下形式:void_assert(inttest,charconst*test_image,charconst*file,intline)(if(!test)(printf("Assertionfailed:%s,file%s,line%d\n",test_image,file,line);abort。;))所以,失敗的斷言在調(diào)用abort。前顯示出失敗情況的診斷條件、出錯(cuò)的源文件名稱和行號。我在這里演示的診斷機(jī)構(gòu)“printf?!毕喈?dāng)粗糙,你所用的運(yùn)行庫的實(shí)現(xiàn)可能產(chǎn)生更多的反饋信息。斷言處理了異常的階段3到5。它們實(shí)際上是一個(gè)帶說明信息的abort。并做了前提條件檢查,如果檢查失敗,程序中止。一般使用斷言調(diào)試邏輯錯(cuò)誤和絕不可能出現(xiàn)在正確的程序中的情況。/不Vnevercalledbyotherprograms*/staticvoidf(int*p)assert(p!=NULL);/*...*/)對比ー下邏輯錯(cuò)誤和可以存在于正確程序中的運(yùn)行期錯(cuò)誤:/*...getfile'name'fromuser...*/FILE*file=fopen(name,mode);assert(file!=NULL);/*questionableuse*/這樣的錯(cuò)誤表示異常情況,但不是bug。對這些運(yùn)行期異常,斷言大概不是個(gè)合適的處理方法,你應(yīng)該用我下面將介紹的另ー個(gè)體系來代替。非局部的跳轉(zhuǎn)與刺激的abort。和exit。相比,goto語句看起來是處理異常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函數(shù)內(nèi)部的標(biāo)號上,而不能將控制權(quán)轉(zhuǎn)移到所在程序的任意地點(diǎn)(當(dāng)然,除非你的所有代碼都在main體中)。為了解決這個(gè)限制,C函數(shù)庫提供了setjmp()和longjmpO函數(shù),它們分別承擔(dān)非局部標(biāo)號和goto作用。頭文件vsetjmp.h>申明了這些函數(shù)及同時(shí)所需的jmp-buf數(shù)據(jù)類型。原理非常簡單:setjmpQ)設(shè)置“jump”點(diǎn),用正確的程序上下文填充jmp_buf對象j。這個(gè)上下文包括程序存放位置、棧和框架指針,其它重要的寄存器和內(nèi)存數(shù)據(jù)。當(dāng)初始化完jump的上下文,setjmp()返回〇值。以后調(diào)用longjmp(j,r)的效果就是ー個(gè)非局部的goto或“長跳轉(zhuǎn)”到由j描述的上下文處(也就是到那原來設(shè)置j的setjmpO處)。當(dāng)作為長跳轉(zhuǎn)的目標(biāo)而被調(diào)用時(shí),setjmp()返冋「或1(如果r設(shè)為〇的話)。(記住,setjmp()不能在這種情況時(shí)返回。。)通過有兩類返冋值,setjmpO讓你知道它正在被怎么使用。當(dāng)設(shè)置j時(shí),setjmpO如你期望地執(zhí)行;但當(dāng)作為長跳轉(zhuǎn)的目標(biāo)時(shí),setjmpO就從外面“喚醒”它的上下文。你可以用longjmp。來終止異常,用setjmpO標(biāo)記相應(yīng)的異常處理程序。#include<setjmp.h>#include<stdio.h>jmp_bufj;voidraise_exception(void)(printf(Mexceptionraised\n");longjmp(j,1);/*jumptoexceptionhandler*/printf(Mthislineshouldneverappear'n");)intmain(void)(if(setjmp(j)==0)printf(,,zsetjmpzisinitializing/j\nM);raise_exception();prinlf("thislineshouldneverappear\nn);)else(printf("zsetjmpxwasjustjumpedinto\nH);/*thiscodeistheexceptionhandler*/)return0;)/*Whenrunyields:'setjmp'isinitializing了exceptionraised"setjmpzwasjustjumpedinto*/那個(gè)填充jmp_buf的函數(shù)不在調(diào)用longjmp。之前返回。否則,存儲在jmp_buf中的上下文就有問題了:jmp_bufj;voidf(void)(setjmp(j);)intmain(void)(f();longjmp(j,1);/*logicerror*/return0;)所以,你必須把setjmp()處理成只是到其所在位置的ー個(gè)非局部跳轉(zhuǎn)。Longjmp。和setjmp。聯(lián)合體運(yùn)行于異常牛.命期的2和3階段。longjmp(j,r)產(chǎn)生異常對象r(ー個(gè)整數(shù)),并且作為返回值傳送到setjmp(j)處。實(shí)際上,setjmp()函數(shù)通報(bào)了異常r。信號C函數(shù)庫也提供了標(biāo)準(zhǔn)的(雖然原始的)''事件”處理包。這個(gè)包定義了一組事件和信號,以及標(biāo)準(zhǔn)的方法來觸發(fā)和處理它們。這些信號或者表示了一個(gè)異常狀態(tài)或者表示了一個(gè)不協(xié)調(diào)的外部事件;基于所談?wù)摰闹黝},我將只集中討論異常信號。為了使用這些包,需要包含標(biāo)準(zhǔn)頭文件vsignal.h〉。這個(gè)頭文件申明了函數(shù)raise。和signal。,數(shù)據(jù)類型sig.atomijt,和以SIG開頭的信號事件宏。標(biāo)準(zhǔn)要求有六個(gè)信號宏,也許你所用的運(yùn)行庫實(shí)的現(xiàn)會再附加ー些。這些信號被固定死在<signal.h>中,你不能增加自定義的信號。信號通過調(diào)用raise。產(chǎn)生并被處理函數(shù)捕獲。運(yùn)行時(shí)體系提供默認(rèn)處理函數(shù),但你能通過signal。函數(shù)安裝自己的處理函數(shù)。處理函數(shù)可以通過sig_atomic_t類型的對象和外部進(jìn)行通訊;如類型名所示,對這樣的對象的操作是原子操作或者說中斷安全的。當(dāng)你掛接信號處理函數(shù)時(shí),通常提供ー個(gè)函數(shù)地址,這個(gè)的函數(shù)必須接受一個(gè)整型值(所要處理的信號事件),并且無返回。這樣,信號處理函數(shù)有些象setjmp();它們所收到的僅有的異常信息是單個(gè)整數(shù):voidhandler(intsignal_value);voidf(void)signal(SIGFPE,handler);/*registerhandler*//*...*/raise(SIGFPE);/*invokehandler,passingit'SIGFPE'*/只可其一地,你可以安裝兩個(gè)特別的處理函數(shù):1 signal(SIGxxx,SIG_DFL),為指定的信號掛接系統(tǒng)的缺省處理函數(shù)。1 signal(SIGxxx,SIG」GN),告訴系統(tǒng)忽略指定的信號。signal。函數(shù)返回前次掛接的處理函數(shù)的地址(表明掛接成功),或返回SIG.ERR(表明掛接失敗)。處理函數(shù)被調(diào)用表明信號正在試圖恢復(fù)異常。當(dāng)然,你可以在處理函數(shù)中隨意地調(diào)用abort。,exit。或longjmp。,有效地將信號解釋為終止異常。有趣的是,abort。自己事實(shí)上在內(nèi)部調(diào)用了raise(SIGABRT)。SIGABRT的缺省處理函數(shù)發(fā)起了一個(gè)診斷并終止程序,當(dāng)然你可以安裝自己的處理函數(shù)來改變這個(gè)行為。不能改變的是abort。的終止程序的行為。Abort。理論上的實(shí)現(xiàn)如下:voidabort(void)raise(SIGABRT);exit(EXIT_FAILURE);也就是說,即使你的SIGABRT處理函數(shù)返回了,abort。仍然中止你的程序。C語言標(biāo)準(zhǔn)在信號處理函數(shù)的行為上增加了一些限制和解釋。如果你有C語言標(biāo)準(zhǔn),我建議你查閱條款7.7.1.1的細(xì)節(jié)。(很不幸,C語言和C++語言的標(biāo)準(zhǔn)在Internet都得不到。)<signaLh>的申明覆蓋了異常的整個(gè)生存期,從產(chǎn)生到死亡。在標(biāo)準(zhǔn)的C語言運(yùn)行期庫中,它們是最接近于異常完全解決方案的。全局變量vsetjmp.h>和<signal.h>一般使用異常通知體系:當(dāng)試圖通知ー個(gè)異常事件時(shí)喚醒ー個(gè)處理函數(shù)。如果你更愿意使用輪詢體系,C標(biāo)準(zhǔn)庫在<errno.h>提供了例子。這個(gè)頭文件定義了errno及其一些可能的取值。標(biāo)準(zhǔn)要求這樣三個(gè)值:EDOM、ERANGE和EILSEQ,分別適用于域、范圍和多字節(jié)順序錯(cuò)誤,你的編譯器可能又加了些其它的,它們?nèi)宰帜浮癊”開頭。errno,通過由運(yùn)行庫的代碼設(shè)置它而用戶代碼查詢它的辦法將二者聯(lián)系起來,運(yùn)行于異常生命期的1到3:運(yùn)行庫產(chǎn)生異常對象(ー個(gè)簡單的整數(shù)),把值拷給errno,然后依賴用戶的代碼去輪詢和檢測這個(gè)異常。運(yùn)行庫主要在vmath.h>和<stdio.h>的函數(shù)中使用errno〇errno在程序開始時(shí)設(shè)為0,函數(shù)庫程序不會再次把它設(shè)為。。因此,要檢測錯(cuò)誤,你必須先將errno設(shè)為0,再調(diào)用運(yùn)行庫程序,調(diào)用完后檢查erm。的值:#include<errno.h>#include<math.h>#include<stdio.h>intmain(void)doublex,y,result;/*...somehowsetxandy...*/errno=0;result=pow(x,y);if(errno==EDOM)printf("domainerroronx/ypair\n");elseif(errno=ERANGE)printf("rangeerroronresult\n");elseprintf("xtothey=%d\n",(int)result);return0;}注意:errno不一定要綁在ー個(gè)對象上:int*_errno_function()(staticintreal_errno=0;return&real_ermo;}#defineerrno(*_ermo_function())intmain(void)errno=0;/*...*/if(errno==EDOM)/*...*/}你可以在自己的程序中采用這樣的技巧,對erm。及其值進(jìn)行模擬。使用C++的話,你當(dāng)然可以把這種策略擴(kuò)展到類或命名空間的對象和函數(shù)上。(實(shí)際上,在C++中,這個(gè)技巧是SingletonPattern的基礎(chǔ)。)返回值和回傳參數(shù)象errno這樣的異常對象不是沒有限制的:1 所有相關(guān)聯(lián)的部分必須一致,確保設(shè)置和檢查同一個(gè)對象。! 無關(guān)的部分可能意外地修改了對象。1 如果沒有在調(diào)用程序前重設(shè)對象,或在調(diào)用下ー步前沒有檢查它們,你就可能漏了異常。! 宏和內(nèi)部代碼中的對象在重名時(shí)將掩蓋異常對象。1 靜態(tài)對象天生就不是(多)線程安全的??傊?,這些對象很脆弱:你太容易用錯(cuò)它們,編譯器沒有警告程序卻有不可預(yù)測的行為。要排除這些不足,你需要這樣的對象:1 被兩個(gè)正確的部分訪問 個(gè)產(chǎn)生異常,ー個(gè)檢測異常。帶有一個(gè)正確的值。! 名字不能被掩蓋1.10線程安全。函數(shù)返回值滿足這些要求,因?yàn)樗鼈兪菬o名的臨時(shí)變量,由函數(shù)產(chǎn)生而只能被調(diào)用者訪問。調(diào)用ー完成,調(diào)用者就可以檢查或拷貝返冋值;然后原始的返冋對象將消失而不能被重用。又因?yàn)槭菬o名的,它不能被掩蓋。(對于C++,我假設(shè)只有右值函數(shù)調(diào)用表達(dá),也就是說不能返回引用。由于我限定現(xiàn)在只談?wù)揅兼容的技巧,而C不支持引用,這樣的假設(shè)是合理的。)返回值出現(xiàn)在異常生命期的階段2。在調(diào)用和被調(diào)用函數(shù)的聯(lián)合體中,這只是完整的異常處理的一部分:intf()(interror;/*...*/if(error)/*Stage1:erroroccurred*/return-1;/*Stage2:generateexceptionobject*//*...*/)intmain(void)(if(f()!=0)/*Stage3:detectexception*/(/*Stage4:handleexception*//*Stage5:recover*/返冋值是C標(biāo)準(zhǔn)庫所喜歡的異常傳播方法??聪旅娴睦?if((p=malloc(n))=NULL)/*...*/if((c=getchar())=EOF)/*...*/if((ticks=clock())<0)/*...*/注意,典型的c習(xí)慣用法:在同條語句中接收返回值和檢測異常。這種壓縮表達(dá)式重載ー個(gè)通道(返回值對象)來攜帶兩個(gè)不同的含義:合法的數(shù)據(jù)值和異常值。代碼必須按兩條路來解釋這個(gè)通道,直到知道哪個(gè)是正確的。這種函數(shù)返回值的用法常見于很多語言中,尤其是Microsoft開發(fā)的語言無關(guān)的ComponentObjectModel(COM)?COM方法通過返冋ー類型為HRESULT(特別安排的32位無符號值)的對象提示異常。和剛討論的例子不同,COM的返回值只攜帶狀態(tài)和異常信息:回傳信息通過參數(shù)列表中的指針進(jìn)行。回傳指針和C++的引用型的參數(shù)是函數(shù)返回值的變形,但有些明顯的不同:! 你能忽略和丟棄返回值?;貍鲄?shù)則綁定到了相應(yīng)的實(shí)參上,所以不可能完全忽略它們。和返回值相比,參數(shù)在函數(shù)和它們的調(diào)用者間形成了緊耦合。1 通過回傳參數(shù)可以返回任意個(gè)數(shù)的值,而通過返回值只能返回一個(gè)值。所以回傳參數(shù)提供了多個(gè)返回值。! 返回值是臨時(shí)對象:它們在調(diào)用前不存在,并且在調(diào)用結(jié)束是消失。實(shí)參的生命期遠(yuǎn)長于函數(shù)的調(diào)用過程。1.11小結(jié)這次大概地介紹了異常和標(biāo)準(zhǔn)C對它的傳統(tǒng)支持。第二部分,我將研究Microsoft對標(biāo)準(zhǔn)C方法的擴(kuò)展:特有的異常處理宏、結(jié)構(gòu)化異常處理或說SEH。我將總結(jié)所有C兼容方法(包括SEH)的局限性,并在第三部分拉開C++異常的序幕。C與C++中的異常處理2(partl)前次,我概述了異常的分類和C標(biāo)準(zhǔn)庫支持的處理方法。這次討論Microsoft對這些方法的擴(kuò)展:結(jié)構(gòu)化異常處理(SEH)和MicrosoftFoundationClass(MFC)異常處理。SEH對C和C++都有效,MFC異常體系只對C++有效。機(jī)構(gòu)化異常處理機(jī)構(gòu)化異常處理是Windows提供的服務(wù)功能并對所有語言寫的程序有效。在VisualC++中,Microsoft封裝和簡化了這些服務(wù)(通過非標(biāo)準(zhǔn)的關(guān)鍵字和庫程序)。Windows平臺的其它編譯器可能選擇不同的方式來到達(dá)相似的結(jié)果。在這個(gè)專欄中,名詞"StructuredExceptionHandling"和"SEH”專指VisualC++對Windows異常服務(wù)的封裝。關(guān)鍵字為了支持SEH,Micorsoft用四個(gè)新關(guān)鍵字?jǐn)U展了C和C++語言:1 _except1 _finally1 _leave1 —try因?yàn)檫@是非標(biāo)關(guān)鍵字,必須打開擴(kuò)展選項(xiàng)后再編譯(關(guān)掉/Fa)。為什么這些關(guān)鍵字帶下劃線?C++標(biāo)準(zhǔn)(條款17.4.3.1.2,“Globalnames")規(guī)定:下列名字和函數(shù)總是保留給編譯器:1 所有帶雙下劃線(_)或以ー個(gè)下劃線加一個(gè)大寫字母開始的名字保留給編譯器隨意使用。1 所有以一個(gè)下劃線開始的名字保留給編譯器作全局名稱用。C標(biāo)準(zhǔn)有類似的申明。既然SEH的關(guān)鍵字符合上面的規(guī)則,Microsoft就有權(quán)這樣使用它們。這也表明,你不被允許在自己的程序中使用保留的名字。你必須避免定義名字類似_MYHEADER_H_或一FatalError的標(biāo)識符。有趣而又不幸地,VisualC++的applicationwizards產(chǎn)生的源代碼使用了保留的標(biāo)識符。例如,如果你用ATLCOMAppWizard生成一個(gè)新的service,結(jié)果框架代碼定義了如一Handler和」winMain的名字ーー標(biāo)準(zhǔn)所說的你的程序不能使用的保留名稱。要減少這個(gè)不合規(guī)定行為,你當(dāng)然可以手工更改這些名稱。還好,這些有疑問的名字都是類的私有變量,在類的定義外面是不可見的,在.h和.cpp中進(jìn)行全局替換是可行的。不幸的是,有一個(gè)函數(shù)(_twinMain)和一個(gè)対象(一Module)被申明了extern,也就是說程序的其它部分會假定你使用了這些名字。(事實(shí)上,VisualC++庫libc.lib在連接時(shí)需要名字_twinMain可用。)我建議你保留Wizard生成的名字,不要在你自己的代碼中定義這樣的名字就可以了。另外,你應(yīng)該將所有不合標(biāo)準(zhǔn)的定義寫入文檔并留給程序的維護(hù)人員;記住,VisualC++以后的版本(和現(xiàn)有的其它C++編譯器)可能以另外的方式使用這些名字,從而破壞了你的代碼。標(biāo)識符Microsoft也在非標(biāo)頭文件excpt.h中定義了幾個(gè)SEH的標(biāo)識符,并且包含入windows.h中。在其內(nèi)部,定義了:! 供_except的過濾表達(dá)式使用的過濾結(jié)果宏。1 Win32對象和函數(shù)的別名宏,用于查詢異常信息和狀態(tài)。! 偽關(guān)鍵字宏,和前面談到的四個(gè)關(guān)鍵字有著相同名字和含義,但沒有下劃線。(例如,宏!eave對應(yīng)SEH關(guān)鍵字_leave。)Microsoft用這些宏令我抓狂。他們對同一個(gè)函數(shù)了定義多個(gè)別名。例如,excpt.h有如下申明和定義:unsignedlong_cdecl_exception_code(void);#defineGetExceptionCode_exception_code#defineexception_code_exception_code也就是說,你可以用三種方法調(diào)用同一函數(shù)。你用哪個(gè)?并且,這些別名會如你所期望地被維護(hù)嗎?在Microsoft的文檔中,它看起來偏愛GetExceptionCode,它的名字和其它全局WindowsAPI函數(shù)風(fēng)格一致。我在MSDN中搜索到33處GetExceptionCode?兩個(gè)_exception_code,而exception_code個(gè)數(shù)為〇〇根據(jù)Microsoft的引導(dǎo),推薦使用GetExceptionCode及類似名稱的其它函數(shù)。因?yàn)?exception.code的兩個(gè)別名是宏,所以你不能再使用同樣的名字了。我曾經(jīng)犯過這個(gè)錯(cuò),當(dāng)我在為這個(gè)專欄寫例程的時(shí)候。我定義了一個(gè)局部對象叫exception_code(大概是吧)。實(shí)際上我就是定義了一個(gè)局部對象叫一exception_co加,這是我無意中使用的宏exception一code展開的結(jié)果。當(dāng)我ー想到是這個(gè)問題,解決方案就是簡單地將我的對象名字從exception_code改為code。最后,excpt.h定義了一個(gè)特別的宏ーー“try”ーー已經(jīng)成為C++真正的關(guān)鍵字的東西。這意味著你不能在包含了excpt.h的編譯單元中簡單地混合SEH和標(biāo)準(zhǔn)C++的異常塊,除非你愿意#undef這個(gè)try宏。當(dāng)這樣undef而露出真正的try關(guān)鍵字時(shí),要冒搞亂SEH的維護(hù)人員大腦的危險(xiǎn)。另ー方面,精通標(biāo)準(zhǔn)C++的程序員會將try理解為ー個(gè)關(guān)鍵字而不是宏。我認(rèn)為,包含ー個(gè)頭文件(即使是象ex叩t.h這樣的非標(biāo)頭文件)不應(yīng)該改變符合語言標(biāo)準(zhǔn)的代碼的行為。我更堅(jiān)持掩蓋或重定義掉語言標(biāo)準(zhǔn)定義的關(guān)鍵字是個(gè)壞習(xí)慣。我建議:#undeftry,同樣不使用其它的偽關(guān)鍵字宏,直接使用真正的關(guān)鍵字(如—try)。語法最基本的SEH語法是try塊。如下形式:_trycompound-statementhandler處理體:_except(filter-expression)compound-statement或._finallycompound-statement完整一點(diǎn)看,try塊如下:—try_except(filter-expression)或:_try_finally在_try里面你必須使用ー個(gè)leave語句:—try_leave;在更大的程序塊中,ー個(gè)try塊被認(rèn)為是個(gè)單條語句:if(x)(_try_finally等價(jià)于:if(x)一try_finally其它注意點(diǎn):1 在給定的try塊中你必須有一個(gè)正確的異常處理函數(shù)。! 所有的語句必須合并。即使只有一條語句跟在_try、_except或_finally后面也必須將它放入{}中。1 在異常處理函數(shù)中,相應(yīng)的過濾表達(dá)式必須有一個(gè)或能轉(zhuǎn)換為ー個(gè)int型的值。1.5基本語意上次我列舉了異常生命期的5個(gè)階段。在SEH體系下,這些階段實(shí)現(xiàn)如下:! 操作系統(tǒng)上報(bào)了一個(gè)硬件錯(cuò)誤或檢測到了一個(gè)軟件錯(cuò)誤,或用戶代碼檢測到ー個(gè)錯(cuò)誤(階段1)。1 (通常是由用戶調(diào)用Win32函數(shù)RasieException啟動,)操作系統(tǒng)產(chǎn)生并觸發(fā)ー個(gè)異常對象(階段2)。這個(gè)對象是一個(gè)結(jié)構(gòu),其屬性對異常處理函數(shù)可見。1 異常處理函數(shù)“看到”異常,并且有機(jī)會捕獲它(階段3和4)。取決于處理函數(shù)的意愿,異常將或者恢復(fù)或者終止。(階段5)。ー個(gè)簡單的例子:intfilter(void)/*Stage4*/intmain(void)—try(if(some_error)/*Stage1*/RaiseException(...);/*Stage2*//*Stage5ofresumingexception*/)_except(filter())/*Stage3*/(/*Stage5ofterminatingexception*/}return0;}Microsoft調(diào)用定義在_except中的異常處理函數(shù),和定義在_finally中的終止函數(shù)。一旦異常被觸發(fā),由一except開始的異常處理函數(shù)被異常發(fā)生點(diǎn)順函數(shù)調(diào)用鏈向外面詢問。每個(gè)被發(fā)現(xiàn)的異常處理函數(shù),其過濾表達(dá)式都被求值。每次求值后發(fā)生什么取決于其返回結(jié)果。excpt.h定義了3個(gè)過濾結(jié)果的宏,都是int型的:1 EXCEPTION_CONTINUE_EXECUTION=-11 EXCEPTION_CONTINUE_SEARCH=01 EXCEPTION_EXECUTE_HANDLER=1前面我說過,過濾表達(dá)式必須兼容int型,所以它們和這3個(gè)宏的值匹配。這個(gè)說法太保守了:我的經(jīng)驗(yàn)顯示VisualC++接受的過濾表達(dá)式可以具有所有的整型、指針型、結(jié)構(gòu)、數(shù)組甚至是void型!(但我在嘗試浮點(diǎn)指針時(shí)遇到了編譯錯(cuò)誤。)更進(jìn)ー步,所有求出的值看來都有效(至少對整型如此)。所有非零且符號位為0的值效果相當(dāng)于EXCEPTION_EXECUTE_HANDLER,而符號位為1的相當(dāng)于EXCEPTION_CONTINUE一EXECUTION。這大概是按位取模的結(jié)果。如果ー個(gè)異常處理函數(shù)的過濾求值結(jié)果是EXCEPTION_CONTINUE_SEARCH,這個(gè)處理函數(shù)拒絕捕獲異常,將繼續(xù)搜索下ー個(gè)異常處理函數(shù)。通過由過濾表達(dá)式產(chǎn)生一個(gè)非EXCEPTION_CONTINUE_SEARCH來捕獲異常,一旦捕獲,程序就恢復(fù)。怎么恢復(fù)仍然由過濾表達(dá)式的值決定:1 EXCEPTION_CONTINUE_EXECUTION:表現(xiàn)為恢復(fù)異常。從發(fā)生異常處下面開始執(zhí)行。異常處理函數(shù)本身的代碼不執(zhí)行。1 EXCEPTION_EXECUTE_HANDLER:表現(xiàn)為終止異常。從異常發(fā)生處開始退棧,ー路上所遇到終止函數(shù)都被執(zhí)行。棧退到捕獲異常的處理函數(shù)所在的?級為止。進(jìn)入處理函數(shù)體并執(zhí)行。如名所示,終止處理函數(shù)(以—finally開始的代碼)在終止異常時(shí)被調(diào)用。里面是cleanup代碼,它們就象C標(biāo)準(zhǔn)庫中的atexit()函數(shù)和C++的析構(gòu)函數(shù)。終止處理函數(shù)在正常執(zhí)行流程也會進(jìn)入,就象不是捕獲型代碼。相反,異常處理函數(shù)總表現(xiàn)為捕獲型:它們只在其過濾表達(dá)式求值為EXCEPTION一EXECUTE一HANDLER時(shí)オ進(jìn)入。終止處理函數(shù)并不明確知道自己是從正常流程進(jìn)入的還是在ー個(gè)try塊異常終止時(shí)進(jìn)入的。要判斷這點(diǎn),可以調(diào)用AbnormalTermination函數(shù)。此函數(shù)返回一個(gè)int,0表明是從正常流程進(jìn)入的,其它值表明在異常終止時(shí)進(jìn)入的。AbnormalTermination實(shí)際上是個(gè)指向—abnormal_termination()的宏。VisualC++將一abnormal_termination()設(shè)計(jì)為環(huán)境敏感的函數(shù),就象一個(gè)關(guān)鍵字。你不能隨便調(diào)用這個(gè)函數(shù),只能在終止處理函數(shù)中調(diào)用。這意味著你不能在終止處理函數(shù)中調(diào)用ー個(gè)中間函數(shù),再在此中間函數(shù)中調(diào)ffl_abnormal_termination().這樣做會得到ー個(gè)編譯期錯(cuò)誤。例程下面的C例子顯示了不同的過濾表達(dá)式值和處理函數(shù)本身類型的相互作用。第一個(gè)版本是個(gè)小的完整程序,以后的版本都在它前面一個(gè)上有小小的改動。所有的版本都自解釋的,你能看清流程和行為。程序通過RaiseException。觸發(fā)ー個(gè)異常對象。RaiseException。函數(shù)的第一個(gè)參數(shù)是異常的代碼,類型是32位無符號整型(DWORD);Microsoft為用戶自定義的錯(cuò)誤保留了[0xE0000000,0xEFFFFFFF]的范圍。其它參數(shù)一般填〇。這里使用的異常過濾器很簡單。實(shí)際使用中,大概要調(diào)用GetExceptionCode。和GetExceptionlnformation。來查詢異常對象的屬性。Version#1:rFerminatingException用VisualC++生成一個(gè)空的Win32控制臺程序,命名為SEH'est,選項(xiàng)為默認(rèn)。將下列C源碼加入工程文件:#include<stdio.h>#include"windows.h"#definefilter(level,status)\(\printf("%s:%*sfilter=>%s\n",\#level,(int)(2*(level)),,M,,#status),\(status)\)#definetermination_trace(level)\printf("%s:%*shandling%snormaltermination\n",\#level,(int)(2*(level)),M,,,\AbnormalTermination()?"ab"ゴ"')staticvoidtrace(intlevel,charconst"message)(printf("%d:%*s%s\n",level,2*level,message);externintmain(void)DWORDconstcode=OxEOOOOOOl;trace(O,"beforefirsttry");trace。,"try");trace(2,"try");trace(3,"try");trace(4,"try");trace(4,"raisingexception");RaiseException(code,0,0,0);trace(4,"afterexception");_finallytermination_trace(4);end_4:trace(3,"continuation1');}_except(filter(3,EXCEPTION_CONTINUE_SEARCH))(trace(3,"handlingexception'1);)trace(2,"continuation");)_finally(termination__trace(2);)trace(l,"continuation");)_except(filter(l,EXCEPTION_EXECUTE_HANDLER))(trace(l,"handlingexception");)trace(0,"continuation");return0;現(xiàn)在編譯代碼。(可能會得到labelend_4未用的警告;先忽略。)注意:1 程序有四個(gè)嵌套try塊,兩個(gè)有異常處理函數(shù),兩個(gè)有終止處理函數(shù)。為了更好地顯示嵌套和控制流程,我把它們?nèi)糠湃胪粋€(gè)函數(shù)中。實(shí)際編程中可能是放在多個(gè)函數(shù)或多個(gè)編譯單元中的。! 追蹤運(yùn)行情況,輸出結(jié)果顯示當(dāng)前塊的嵌套層次。1 異常過濾器被實(shí)現(xiàn)為宏。第一個(gè)參數(shù)是嵌套層次,第二個(gè)オ是實(shí)際要處理的值。! 終止處理函數(shù)通過termination_trace宏跟蹤其執(zhí)行情況,顯示出調(diào)用它們的原因。(記住,終止處理函數(shù)即使沒有發(fā)生異常也會進(jìn)入的。)運(yùn)行此程序,將看到如下輸出:0:beforefirsttrytrytrytrytryraisingexceptionfilter=>EXCEPTION_CONTINUE_SEARCHfilter=>EXCEPTION_EXECUTE_HANDLERhandlingabnormaltermination2:handlingabnormaltermination1:handlingexception0:continuation事件鏈:1 第四層try塊觸發(fā)了一個(gè)異常。這導(dǎo)致順嵌套鏈向上搜索,查找愿意捕獲這個(gè)異常的異常過濾器。1 碰到的第一個(gè)異常過濾器(在第三層)得出了EXCEPTION_CONTINUE_SEARCH,所以拒絕捕獲這個(gè)異常。繼續(xù)搜索下ー個(gè)異常處理函數(shù)。! 碰到的下ー個(gè)異常過濾器(在第一層)得出了EXCEPTION-EXECUTE-HANDLER。這次,這個(gè)過濾器捕獲這個(gè)異常。因?yàn)樗蟮玫闹?異常將被終止。1 控制權(quán)回到異常發(fā)生點(diǎn),開始退棧。沿路所有的終止處理函數(shù)被運(yùn)行,并且所有的處理函數(shù)都知道異常終止發(fā)生了。?直退棧到控制權(quán)回到捕獲異常的異常處理函數(shù)(在第ー層)。在退棧時(shí),只有終止處理函數(shù)被執(zhí)行,中間的其它代碼被忽略??刂茩?quán)一回到捕獲異常的異常處理函數(shù)(在第一層),將以正常狀態(tài)繼續(xù)執(zhí)行。注意,控制權(quán)在同一嵌套層傳遞了兩次:第一次異常過濾表達(dá)式求值,第二次在退棧和執(zhí)行終止處理函數(shù)時(shí)。這造成了一種危害可能:如果ー個(gè)異常過濾表達(dá)式以某種終止處理函數(shù)不期望的方式修改了的什么。ー個(gè)基本原則就是,你的異常過濾器不能有副作用;如果有,則必須為你的終止處理函數(shù)保存它們。1.8版本2:未捕獲異常將例程中的這行:_except(filter(l,EXCEPTION一EXECUTE_HANDLER))改為_except(filter(l,EXCEPTION一CONTINUE一SEARCH))于是沒有異常過濾器捕獲這個(gè)異常。執(zhí)行修改后的程序,你將看到:0:beforefirsttry2:try2:trytryraisingexception3: filter=>EXCEPTION_CONTINUE_SEARCH1:filter=>EXCEPTION_CONTINUE_SEARCH接著出現(xiàn)這個(gè)對話框:.用戶異常對話框點(diǎn)“Details”將其展開.用戶異常對話框的詳細(xì)信息在出錯(cuò)信息中可看到:出錯(cuò)程序是SEH_TEST,通過RaiseException拋出的原始異常碼是eOOOOOOlHo這個(gè)異常漏出了程序,最后被操作系統(tǒng)捕獲和處理。有些象你的程序是這么寫的:_try(intmain(void)except(exception_dialog(),EXCEPTION_EXECUTE_HANDLER)按對話框上的“Close”,所有的終止處理函數(shù)被執(zhí)行,并退棧,直到控制權(quán)回到捕獲異常的處理函數(shù)。你可以明顯看到這些信息:handlingabnormaltermination2: handlingabnormaltermination它們出現(xiàn)在關(guān)閉對話框之后。注意,你沒有看到:O:continuation因?yàn)樗膶?shí)現(xiàn)代碼在終止處理函數(shù)之外,而退棧時(shí)只有終止處理函數(shù)被執(zhí)行。對我們的試驗(yàn)程序而言,捕獲異常的處理函數(shù)在main之外,這意味著傳遞異常的行為到了程序范圍外仍然在繼續(xù)。其結(jié)果是,程序被終止了。C與C++中的異常處理2(part2)1.1版本3:恢復(fù)異常接下來,改:_except(except_filter(3,EXCEPTION一CONTINUE一SEARCH))為:_except(except_filter(3,EXCEPTION_CONTINUE_EXECUTION))重新編譯并運(yùn)行??梢钥吹竭@樣的輸出:0:beforefirsttry1:trytrytrytryraisingexceptionfilter=>EXCEPTION_CONTINUE_EXECUTIONafterexceptionhandlingnormal termination3: continuation2: continuation2: handlingnormalterminationcontinuationOxontinuation因?yàn)榈谌龑拥漠惓_^濾器己經(jīng)捕獲了異常,第一層的過濾器不會被求值。捕獲異常的過濾器求值為EXCEPTION-CONTINUE-EXECUTION,因此異常被恢復(fù)。異常處理函數(shù)不會被進(jìn)入,將從異常發(fā)生點(diǎn)正常執(zhí)行下去。1.2版本4:異常終止這樣的結(jié)構(gòu):_tryreturn;或:_try(/*...*/gotolabel;)—finally(/*...*/)/*...*/label:被認(rèn)為是iry塊異常終止。以后調(diào)用AbnormalTerminalion。函數(shù)的話將返回非。值,就象異常仍然存在。要看其效果,改這兩行:trace(4,"raisingexception");RaiseException(exception__code,0,0,0);為:trace(4,"exitingtryblock");gotoend_4;第4層的2塊不是被ー個(gè)異常結(jié)束的,現(xiàn)在是被goto語句結(jié)束的。運(yùn)行結(jié)果:0:beforefirsttrytrytrytrytryexitingtryblock4: handlingabnormaltermination3: continuation2: continuation2: handlingnormalterminationcontinuation0:continuation第4層的終止處理函數(shù)認(rèn)為它正在處理異常終止,雖然并沒有發(fā)生過異常。(如果發(fā)生過異常的話,我們至少能從ー個(gè)異常過濾器的輸出信息上看出來的。)結(jié)論:你不能只依賴AbnormalTermination。函數(shù)來判斷異常是否仍存在。1.3版本5:正常終止如果想正常終止ー個(gè)try塊,也就是想要AbnormalTermination()函數(shù)返回FALSE,應(yīng)該使用Microsoft特有的關(guān)鍵字_leave〇想驗(yàn)證的話,改:gotoend_4;為:—leave;重新編譯并運(yùn)行,結(jié)果是:O:beforefirsttrytrytrytrytryexitingtryblock4: handlingnormaltermination3: continuation2: continuation2: handlingnormalterminationcontinuationO:continuation和版本4的輸出非常接近,除了一點(diǎn):第4層的終止處理函數(shù)現(xiàn)在認(rèn)為它是在處理正常結(jié)束。1.4版本6:隱式異常前面的程序版本處理的都是用戶產(chǎn)生的異常。SEH也可以處理Windows自己拋出的異常。改這行:trace(4,"exitingtryblock'1);—leave;為:trace(4,"implicitlyraisingexception");*((char*)0)='x';這導(dǎo)致Windows的內(nèi)存操作異常(引用空指針)。接著改:_except(except_filter(3,EXCEPTION_CONTINUE_EXECUTION))為:_except(except_filter(3,EXCEPTION_EXECUTE_HANDLER))以使程序捕獲并處理異常。執(zhí)行結(jié)果為:0:beforefirsttrytrytrytrytryfilter=>EXCEPTION_EXECUTE_HANDLERhandlingabnormaltermination3: handlingexception2: continuation2: handlingnormalterminationcontinuationO:continuation如我們所預(yù)料,Windows在嵌套層次4中觸發(fā)了一個(gè)異常,并被層次3的異常處理函數(shù)捕獲。如果你想知道捕獲的精確異常碼,可以讓異常傳到main外面去,就象版本2中做的。為此,改:_except(except_filter(3,EXCEPTION_EXECUTE_HANDLER))為:_except(except_filter(3,EXCEPTION_CONTINUE_SEARCH))結(jié)果對話框在按了“Details”后,顯示的信息非常象用戶異常。圖3內(nèi)存異常對話框和版本2的對話框不同是,上次顯示了特別的異常碼,這次說了“invalidpagefaulビー一更用戶友好些吧。C++考慮事項(xiàng)在所有C兼容異常處理體系中,SEH無疑是最完善和最靈活的(至少在Windows環(huán)境下)。具有諷刺意味的,它也是Windows體系以外的環(huán)境中最不靈活的,它將你和特殊的運(yùn)行平臺及VisaulC++源碼兼容的編譯器牢牢綁在了一起。如果只使用C語言,并且不考慮移植到Windows平臺以外,SEH很好。但如果使用C++并考慮可移植性,我強(qiáng)烈建議你使用標(biāo)準(zhǔn)C++異常處理而不用SEHo你可以在同一個(gè)程序中同時(shí)使用SEH和標(biāo)準(zhǔn)C++異常處理,只有一個(gè)限制:如果在有SEHtry塊的函數(shù)中定義了一個(gè)對象,而這個(gè)對象又沒有non-trivial(無行為的)析構(gòu)函數(shù),編譯器會報(bào)錯(cuò)。在同一函數(shù)中同時(shí)使用這樣的對象和SEH的—try,你必須禁掉標(biāo)準(zhǔn)C++異常處理。(VisualC++默認(rèn)關(guān)掉標(biāo)準(zhǔn)C++異常處理。你可以使用命令行參數(shù)/GX或VisualStudio的ProjectSettings對話框打開它。)在以后的文章中,我會在討論標(biāo)準(zhǔn)C++異常處理時(shí)回顧SEH。我想將SEH整合入C++的主流中,通過將結(jié)構(gòu)化異常及Windows運(yùn)行庫支持映射為C++異常和標(biāo)準(zhǔn)C++運(yùn)行庫支持。MFC異常處理說明:這ー節(jié)我需要預(yù)先引用ー點(diǎn)點(diǎn)標(biāo)準(zhǔn)C++異常處理的知識,但要到下次オ正式介紹它們。這個(gè)提前引用是不可避免的,也是沒什么可驚訝的,因?yàn)镸icrosoft將它們的MFC異常的語法和語義構(gòu)建在標(biāo)準(zhǔn)C++異常的語法和語義的基礎(chǔ)上。我到現(xiàn)在為止所講的異常處理方法對C和C++都有效。在此之外,Microsoft對C++程序還有一個(gè)解決方案:MFC異常處理類和宏。Microsoft現(xiàn)在認(rèn)為MFC異常處理體系過時(shí)了,并鼓勵你盡可能使用標(biāo)準(zhǔn)C++異常處理。然而VisualC++仍然支持MFC異常類和及宏,所以我將給它個(gè)簡單介紹。Microsoft用標(biāo)準(zhǔn)C++異常實(shí)現(xiàn)了MFC3.0及以后版本。所以你必須激活標(biāo)準(zhǔn)C++異常才能使用MFC,即使你不打算顯式地使用這些異常。前面說過,你必須禁掉標(biāo)準(zhǔn)C++異常來使用SEH,這也意味著你不能同時(shí)使用MFC宏和SEHoMicrosoft明文規(guī)定這兩個(gè)異常體系是互斥的,不能在同一程序中混合使用。SEH是擴(kuò)展了編譯器關(guān)鍵字集,MFC則定義了一組宏:1 TRY1 CATCH,AND_CATCH,和END_CATCH1 THROW和THROW_LAST這些宏非常象C++的異常關(guān)鍵字try,catch和throw〇另外,MFC提供了異常類體系。所有名字為CXXXException形式的類都是從抽象類CException派生的。這類似于標(biāo)準(zhǔn)C++運(yùn)行庫在vsetdxcept>中申明的從std:exception開始的派生體系。但,標(biāo)準(zhǔn)C++的關(guān)鍵字可以處理絕大部分類型的異常對象,而MFC宏只能處理CException的派生類型對象。AfxThrowXXXException(),它構(gòu)造、初始化和拋出這個(gè)類的對象。你可以用這些輔助函數(shù)處理預(yù)定義的異常類型,用THROW處理自定義的對象(當(dāng)然,它們必須是從CException派生的)?;镜脑O(shè)計(jì)原則是:! 用TRY塊包含可能產(chǎn)生異常的代碼。! 用CATCH檢測并處理異常。異常處理函數(shù)并不是真的捕獲對象,它們其實(shí)是捕獲了指向異常的指針。MFC靠動態(tài)類型來辨別異常對象。比較ー下,SEH靠運(yùn)行時(shí)查詢異常碼來辨別異常。! 可以在ー個(gè)TRY塊上捆綁多個(gè)異常處理函數(shù),每個(gè)捕獲ー個(gè)C++靜態(tài)類型不同的對象。第一個(gè)處理函數(shù)使用宏CATCH,以后的使用AND.CATCH,用END.CATCH結(jié)束處理函數(shù)隊(duì)列。1 MFC自己可能觸發(fā)異常,你也可以顯式觸發(fā)異常(通過THROW或MFC輔助函數(shù))。在異常處理函數(shù)內(nèi)部,可以用THROW.LAST再次拋M最近一次捕獲的異常。1 異常?被觸發(fā),異常處理函數(shù)就將被從里到外進(jìn)行搜索,和SEH時(shí)ー樣。搜索停止于找到一個(gè)類型匹配的異常處理函數(shù)。所有異常都是終止。和SEH不一樣,MFC沒有終止處理函數(shù),你必須依賴于局部對象的析構(gòu)函數(shù)。ー個(gè)小MFC例子,將大部分題目都包括了:#include<stdio.h>#include"afxwin.h"voidf()TRYprintf("raisingmemoryexception\n");AfxThrowMemoryException();printf("thislineshouldneverappear\n");CATCH(CException,e)printf("caughtgenericexception;rethrowing\n");THROW_LAST();printf("thislineshouldneverappear\n");)END.CATCHprintf("thislineshouldneverappear'n”);}intmain()(TRY{f();printf("thislineshouldneverappear\nH);)CATCH(CFileException,e)(printf("caughtfileexception\n'f);AND_CATCH(CMemoryException,e)printf("caughtmemoryexception\n");}/*...handlersforotherCException-derivedtypes...*/AND_CATCH(CException,e)(printf("caughtgenericexception^”);}END_CATCHreturn0;)儼Whenrunyieldsraisingmemoryexceptioncaughtgenericexception;rethrowingcaughtmemoryexception*/記住,異常處理函數(shù)捕獲指向?qū)ο蟮闹羔?而不是實(shí)際的對象。所以,處理函數(shù):CATCH(CException,e)定義了一個(gè)局部指針CException*e指向了被拋出的異常對象?;贑++的多態(tài),這個(gè)指針可以引用任何從CException派生的對象。如果同一回塊有多個(gè)處理函數(shù),它們按從上到下的順序進(jìn)行匹配搜索的。所以,你應(yīng)該將處理最派生類的對象的處理函數(shù)放在前面,不然的話,更派生類的處理函數(shù)不會接收任何異常的(再次拜多態(tài)所賜)。因?yàn)槟愕湫偷叵氩东@CException,MFC定義了幾個(gè)CException特有宏:1 CATCH一ALL(e)和AND_CATCH_ALL(e),等價(jià)于CATCH(CException,e)和AND_CATCH(CException,e)。1 END_CATCH_ALL.結(jié)束CATCH一ALL...AND一CATCH一ALL隊(duì)歹リ。1 END.TRY等價(jià)于CATCH_ALL(e);END_CATCH_ALLo這讓TRY...END.TRY中沒有處理函數(shù)或說是接收所有拋出的異常。這個(gè)被指的異常對象由MFC隱式析構(gòu)和歸還內(nèi)存。這一點(diǎn)和標(biāo)準(zhǔn)C++異常處理函數(shù)不ー樣,MFC異常處理不會讓任何人取得被捕獲的指針的所有權(quán)。因此,你不能用MFC和標(biāo)準(zhǔn)C++體系同時(shí)處理相同的異常對象;不然的話,將導(dǎo)致內(nèi)存泄漏:引用已被析構(gòu)的對象,并重復(fù)析構(gòu)和歸還同一對象。1.7小結(jié)MSDN在線還有另外幾篇探索結(jié)構(gòu)化異常處理和MFC異常宏的文章。下次我將介紹標(biāo)準(zhǔn)C++異常,概述它們的特點(diǎn)及基本原理。我還會將它們和到現(xiàn)在一經(jīng)看到的方法進(jìn)行比較。C++異常處理的基本語法和語義這次,我來概述標(biāo)準(zhǔn)C++異常處理的基本語法和語義。順便,我會將它和前兩次提到的技術(shù)進(jìn)行比較。(在本文及以后,我將標(biāo)準(zhǔn)C++異常處理簡稱為EH,將微軟的方法稱為SEH。)1.1 基本語法和語義EH引入了3個(gè)新的C++語言關(guān)鍵字:1 catch1 throw1 try異常通過如下語句觸發(fā)throw[expression]函數(shù)通過“異常規(guī)格申明”定義它將拋出什么異常:throw([type-ID-list])可選項(xiàng)type-ID-list包含ー個(gè)或多個(gè)類型的名字,以逗號分隔。這些異??縯ry塊中的異常處理函數(shù)進(jìn)行捕獲。trycompound-statementhandler-sequence處理函數(shù)隊(duì)列包含一個(gè)或多個(gè)處理函數(shù),形式如下:catch(exception-declaration)compound-statement處理函數(shù)的“異常申明”指明了這個(gè)函數(shù)將捕獲什么類型的異常。和SEHー樣,跟在try和catch后面的語句必須刮在{}內(nèi),而整個(gè)try塊組成一條完整的大語句。例子:voidf()throw(int,some_class_type)(inti;//...generatean'inビexceptionthrowi;〃…}intmain()(tryf();catch(inte)(//...handle'ini'exception...}catch(some_class_typee)(//...handle'some_class_type'exception...)//...possiblyotherhandlers...return0;}異常規(guī)格申明是EH特有的,SEH和MFC都沒有類似的東西。ー個(gè)空的異常規(guī)格申明表明函數(shù)不拋出任何異常:voidf()throw()//...functionthrowsnoexceptions...如果函數(shù)沒有異常規(guī)格申明,它可以拋出任何類型的異常:voidf()(//...functioncanthrowanythingornothing...}當(dāng)函數(shù)拋異常時(shí),關(guān)鍵字throw通常后面帶ー個(gè)被拋出的對象:throwi;然而,throw也可以不帶對象:catch(inte)(//...handlezintzexception...throw;)它的效果是再次拋出當(dāng)前正被捕獲的對象(inte)=因?yàn)榭誸hrow的作用是再次拋出已存在的異常對象,所以它必須位于catch語句塊中。MFC也有再次拋出異常的功能,SEH則沒有,它沒有將異常對象交給過處理函數(shù),所以沒什么可再次拋出的。就象函數(shù)原型中的參數(shù)申明一樣,異常申明也可以是無名的:catch(char*)//...handle'char*'exception...
當(dāng)這個(gè)處理函數(shù)捕獲ー個(gè)char?型的異常對象時(shí),它不能操作這個(gè)對象,因?yàn)檫@個(gè)對象沒有名字。異常申明還可以是這樣的特殊形式:catch(...)//...handleanytypeofexception...就象不定參數(shù)中的-樣,異常申明中的“...”可以匹配任何異常的類型。標(biāo)準(zhǔn)異常對象的類型標(biāo)準(zhǔn)庫函數(shù)可能報(bào)告錯(cuò)誤。在C標(biāo)準(zhǔn)庫中的報(bào)錯(cuò)方式在前面說過了。在C++標(biāo)準(zhǔn)庫中,有些函數(shù)拋出特定的異常,而另外一些根本不拋任何異常。因?yàn)镃++標(biāo)準(zhǔn)中沒有明確規(guī)定,所以C++的庫函數(shù)可以拋出任何對象或不拋。但C++標(biāo)準(zhǔn)推薦運(yùn)行庫的實(shí)現(xiàn)通過拋出定義在<stdexecpt>中的異常類型或其派生類型來報(bào)告錯(cuò)誤:namespacestdclasslogic_error;//:publicexceptionclasslogic_error;//:publicexceptionclassdomain_error;//:publiclogic_errorclassdomain_error;//:publiclogic_errorclassinvalid_argument;//:publiclogic_errorclasslength__error;//:publiclogic_errorclasslength__error;//:publiclogic_errorclassout_ofLrange;//:publiclogic_errorclassout_ofLrange;//:publiclogic_errorclassruntime-error;//classruntime-error;//:publicexceptionclassrange_error;//:publicruntime_errorclassrange_error;//:publicruntime_errorclassoverflow_error;//:publicruntime_errorclassunderflow_error;//:publicruntime_error)這些(異常)類只對C++標(biāo)準(zhǔn)庫有約束カ。在你自己的代碼中,你可以拋出(和捕獲)任何你所象要的類型。標(biāo)準(zhǔn)中的其它申明標(biāo)準(zhǔn)庫頭文件〈exception〉申明了幾個(gè)EH類型和函數(shù)namespacestd(////types//classbad_exception;classexception;typedefvoid(*terminate_handler)();typedefvoid(*unexpected_handler)();////functions//terminate_handlerset_terminate(terminate__handler)throw();unexpected_handlerset_unexpected(unexpected_handler)throw();voidterminate();voidunexpected();booluncaught_exception();提耍:1 exception是所有標(biāo)準(zhǔn)庫拋出的異常的基類。I uncaught_exception()函數(shù)在有異常被拋出卻沒有被捕獲時(shí)返回true,其它情況返回false。它類似于SEH的函數(shù)AbnormalTermination()o1 terminate。是EH的應(yīng)急處理。它在異常處理體系陷入了不可恢復(fù)狀態(tài)時(shí)被調(diào)用,經(jīng)常是因?yàn)樵噲D重入(在前ー個(gè)異常正處理過程中又拋了一個(gè)異常)。1 unexpected。在函數(shù)拋出ー個(gè)它沒有在“異常規(guī)格申明”中申明的異常時(shí)被調(diào)用。這個(gè)預(yù)料外的異常可能在退棧過程中被替換為ー個(gè)bad_excetion對象。運(yùn)行庫提供了缺省terminate_handler。和unexpected_handler。函數(shù)處理對應(yīng)的情況。你可以通過set_terminate。和set_unexpected。函數(shù)替換庫的默認(rèn)版本。1.4 異常生命期EH運(yùn)行于異常生命期的五個(gè)階段:程序或運(yùn)行庫遇到ー個(gè)錯(cuò)誤狀況(階段1)并且拋出ー個(gè)異常(階段2)。! 程序的運(yùn)行停止于異常點(diǎn),開始搜索異常處理函數(shù)。搜索沿調(diào)用棧向上搜索(很象SEH終止異常時(shí)的行為)。搜索結(jié)束于找到了一個(gè)異常申明與異常對象的靜態(tài)類型相匹配(階段3)。于是進(jìn)入相應(yīng)的異常處理函數(shù)。1 異常處理函數(shù)結(jié)束后,跳到此異常處理函數(shù)所在的try塊下面最近的一條語句開始執(zhí)行(階段5)。這個(gè)行為意味著C++標(biāo)準(zhǔn)中異??偸墙K止。這些步驟演示于這個(gè)簡單的例子中:#include<stdio.h>staticvoidf(intn)if(n!=0)//Stage1throw123;//Stage2externintmain()f(l);printf("resuming,shouldneverappear\n");)catch(int)//Stage3(//Stage4printf("caught'inビexception\n");)catch(char*)//Stage3(//Stage4printf("caught'char*'exception\n");catch(...)//Stage3//Stage4printf("caughttypelessexception\n");//Stage5printf("terminating,after'try'block\n");return0;Whenrunyieldscaught'int'exceptionterminating,after'try'block*/1.5基本原理C標(biāo)準(zhǔn)庫的異常體系處理C++語言時(shí)有如ド難題:1 析構(gòu)函數(shù)被忽略。既然C標(biāo)準(zhǔn)庫異常體系是為C語言設(shè)計(jì)的,它們不知道C++的析構(gòu)函數(shù)。尤其,abort。、exit。和longjmp()在退?;虺绦蚪K止時(shí)不調(diào)用局部對象的析構(gòu)函數(shù)。I 繁瑣的。查詢?nèi)謱ο蠡蚝瘮?shù)返回值導(dǎo)致了代碼混亂ー你必須在所有可能發(fā)生異常的地方進(jìn)行明確的異常情況檢測,即使是異常情況可能實(shí)際上從不發(fā)生。因?yàn)檫@種方法是如此繁瑣,程序員們可能會故意“忘了”檢測異常情況。! 無彈性的。tongjmpO"拋出"的只能是簡單的int型。errno和signal()/raise()只使用了很小的一個(gè)值域集合,分辨率很低。Abort。和exit()總是終止程序。Assert。只工作在debug版本中。! 非固有的。所有的C標(biāo)準(zhǔn)庫異常體系都需要運(yùn)行庫的支持,它不是語言內(nèi)核支持的。微軟特有的異常處理體系也不是沒有限制的:1 SEH異常處理函數(shù)不是直接捕獲ー個(gè)異常對象,而是通過查詢ー個(gè)(概念性的)類似ermo的全局值來判斷什么異常發(fā)生了。1 SEH異常處理函數(shù)不能組合,給定try塊的唯有的一個(gè)處理函數(shù)必須在運(yùn)行期識別和處理所有的異常事件。1 MFC異常處理函數(shù)只能捕獲CException及派生類型的指針。1 通過包含定義了MFC異常處理函數(shù)的宏的頭文件,程序包含了數(shù)百個(gè)無關(guān)的宏和申明。1 MFC和SEH都是專屬于與Microsoft兼容的開發(fā)環(huán)境和Windows運(yùn)行平臺的。標(biāo)準(zhǔn)C++異常處理避免了這些短處:1 析構(gòu)安全。在拋異常而進(jìn)行退棧時(shí),局部對象的析構(gòu)函數(shù)被按正確的順序調(diào)用。! 不引人注目的。異常的捕獲是暗地里的和自動的。程序員無需因錯(cuò)誤檢測而搞亂設(shè)計(jì)。1 精確的。因?yàn)閹缀跞魏螌ο蠖伎梢员粧?/p>
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 重冶制團(tuán)制粒工崗前工作技巧考核試卷含答案
- 松香蒸餾工安全生產(chǎn)意識模擬考核試卷含答案
- 農(nóng)藥使用培訓(xùn)員操作技能競賽考核試卷含答案
- 紫膠生產(chǎn)工安全生產(chǎn)意識競賽考核試卷含答案
- 機(jī)制砂石骨料生產(chǎn)工崗前基礎(chǔ)技能考核試卷含答案
- 漁船機(jī)駕長崗后測試考核試卷含答案
- 假肢裝配工安全知識競賽強(qiáng)化考核試卷含答案
- 2025年上海立信會計(jì)金融學(xué)院輔導(dǎo)員考試筆試真題匯編附答案
- 2025吉林省長春市公務(wù)員考試數(shù)量關(guān)系專項(xiàng)練習(xí)題及答案1套
- 電光源外部件制造工誠信品質(zhì)模擬考核試卷含答案
- 2026年陜西省森林資源管理局局屬企業(yè)公開招聘工作人員備考題庫帶答案詳解
- 規(guī)范園區(qū)環(huán)保工作制度
- 2026廣東深圳市龍崗中心醫(yī)院招聘聘員124人筆試備考試題及答案解析
- 2025年同工同酬臨夏市筆試及答案
- 2026年孝昌縣供水有限公司公開招聘正式員工備考題庫及答案詳解(考點(diǎn)梳理)
- 2026屆新高考語文熱點(diǎn)沖刺復(fù)習(xí) 賞析小說語言-理解重要語句含意
- 集資入股協(xié)議書范本
- 天津市部分區(qū)2024-2025學(xué)年九年級上學(xué)期期末練習(xí)道德與法治試卷(含答案)
- 統(tǒng)編版六年級語文上冊:閱讀理解知識點(diǎn)+答題技巧+練習(xí)題(含答案)
- JJG 521-2024 環(huán)境監(jiān)測用X、γ輻射空氣比釋動能率儀檢定規(guī)程
- 采購部管理評審總結(jié)
評論
0/150
提交評論