• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            大龍的博客

            常用鏈接

            統(tǒng)計(jì)

            最新評論

            異常處理 ------ 轉(zhuǎn)

            潛心研究C++異常處理機(jī)制數(shù)日,有所得,與大家共享:

            C++異常處理機(jī)制核心觀點(diǎn):

            0.如果使用普通的處理方式:ASSERT,return等已經(jīng)
               足夠簡潔明了,請不要使用異常處理機(jī)制.

            1.比C的setjump,longjump優(yōu)秀.

            2.可以處理任意類型的異常.
               你可以人為地拋出任何類型的對象作為異常.
               throw 100;
               throw "hello";
               ...

            3.需要一定的開銷,頻繁執(zhí)行的關(guān)鍵代碼段避免使用
               C++異常處理機(jī)制.

            4.其強(qiáng)大的能力表現(xiàn)在:
               A.把可能出現(xiàn)異常的代碼和異常處理代碼隔離開,結(jié)構(gòu)更清晰.
               B.把內(nèi)層錯(cuò)誤的處理直接轉(zhuǎn)移到適當(dāng)?shù)耐鈱觼硖幚?化簡了處理
                 流程.傳統(tǒng)的手段是通過一層層返回錯(cuò)誤碼把錯(cuò)誤處理轉(zhuǎn)移到
                上層,上層再轉(zhuǎn)移到上上層,當(dāng)層數(shù)過多時(shí)將需要非常多的判斷,
                以采取適當(dāng)?shù)牟呗?
               C.局部出現(xiàn)異常時(shí),在執(zhí)行處理代碼之前,會執(zhí)行堆棧回退,即為
                 所有局部對象調(diào)用析構(gòu)函數(shù),保證局部對象行為良好.
               D.可以在出現(xiàn)異常時(shí)保證不產(chǎn)生內(nèi)存泄漏.通過適當(dāng)?shù)膖ry,catch
                 布局,可以保證delete pobj;一定被執(zhí)行.

               E.在出現(xiàn)異常時(shí),能夠獲取異常的信息,指出異常原因.
                 并可以給用戶優(yōu)雅的提示.
               F.可以在處理塊中嘗試錯(cuò)誤恢復(fù).保證程序幾乎不會崩潰.
                 通過適當(dāng)處理,即使出現(xiàn)除0異常,內(nèi)存訪問違例,也能
                 讓程序不崩潰,繼續(xù)運(yùn)行,這種能力在某些情況下及其重要.

            以上ABCDEF可以使你的程序更穩(wěn)固,健壯,不過有時(shí)讓程序崩潰似乎更
            容易找到原因,程序老是不崩潰,如果處理結(jié)果有問題,有時(shí)很難查找.

            5.并不是只適合于處理'災(zāi)難性的'事件.普通的錯(cuò)誤處理也可以用異常機(jī)制
               來處理,不過如果將此濫用的話,可能造成程序結(jié)構(gòu)混亂,
               因?yàn)楫惓L幚頇C(jī)制本質(zhì)上是程序處理流程的轉(zhuǎn)移,不恰當(dāng)?shù)?過度的轉(zhuǎn)移顯然
               將造成混亂.許多人認(rèn)為應(yīng)該只在'災(zāi)難性的'事件上使用異常處理,以避免異常
               處理機(jī)制本身帶來的開銷,你可以認(rèn)為這句話通常是對的.

            6.先讓程序更脆弱,再讓程序更堅(jiān)強(qiáng).首先,它使程序非常脆弱,稍有差錯(cuò),馬上
               執(zhí)行流程跳轉(zhuǎn)掉,去尋找相應(yīng)的處理代碼,以求適當(dāng)?shù)慕鉀Q方式.
               很像一個(gè)人身上帶著許多藥品,防護(hù)工具出行,稍有頭暈,馬上拿出清涼油;
               遇到蚊子立刻拿出電蚊拍滅之.

            WINDOWS:
            7.將結(jié)構(gòu)化異常處理結(jié)合/轉(zhuǎn)換到C++異常對象,可以更好地處理WINDOWS程序
               出現(xiàn)的異常.
            8.盡一切可能使用try,catch,而不是win32本身的結(jié)構(gòu)化異常處理或者
               MFC中的TRY,CATCH宏.

            用得恰到好處,方顯C++異常之美妙!

            1.         異常處理的使用

            首先說明,千萬別對異常處理鉆牛角尖,那樣會死人的(當(dāng)然是煩死的)!

            在C++編程處理中,我秉承這樣一個(gè)思想,就是:能不用異常處理的就不用。因?yàn)樵斐傻幕靵y實(shí)在是太——多了。如果能 用其他方法捕捉到錯(cuò)誤并處理的話,誓死不用異常處理!呵呵,或許有點(diǎn)偏激,但我認(rèn)為,這不失為一個(gè)避免不必要的錯(cuò)誤的一個(gè)好辦法。當(dāng)什么分配內(nèi)存失敗,打 開文件失敗之類的通常錯(cuò)誤,我們只需用assert,abort之類的函數(shù)就解決問題了。也就是說,假如有足夠的信息去處理一個(gè)錯(cuò)誤,那么這個(gè)錯(cuò)誤就不是 異常。

                    當(dāng)然了,異常處理的存在也有它本身的意義和作用。不是你說不用就不用的,有些地方還非得用不可!

                    比如說,在當(dāng)前上下文環(huán)境中,無法捕捉或確定的錯(cuò)誤類型,我們就得用一個(gè)異常拋出到更大的上下文環(huán)境當(dāng)中去。還有,異常處理的使用呢,可以使出錯(cuò)處理程序與“通常”代碼分離開來,使代碼更簡潔更靈活。另外就是程序必不可少的健壯性了,異常處理往往在其中扮演著重要的角色。

                    OK,下面闡述一下。

            2.         拋出異常

            關(guān)——鍵字(周星馳的語氣):throw

            例——句:throw ExceptionClass(“oh, shit! it’s a exception!L “);

            例句中,ExceptionClass是一個(gè)類,它的構(gòu)造函數(shù)以一個(gè)字符串做為參數(shù),用來說明異常。也就是說,在throw的時(shí)候,C++的編譯器先構(gòu)造一個(gè)ExceptionClass的對象,讓它作為throw的返回值,拋——出去。同時(shí),程序返回,調(diào)用析構(gòu)。看下面這個(gè)程序:

            #include <iostream.h>

            class ExceptionClass{

                    char* name;

            public:

                    ExceptionClass(char* name="default name")        {

                           cout<<"Construct "<<name<<endl;

                           this->name=name;

                    }

                    ~ExceptionClass()        {

                           cout<<"Destruct "<<name<<endl;

                    }

                    void mythrow(){

                    throw ExceptionClass("o,my god");

            }       

            };

            void main(){

                    ExceptionClass e("haha");

                    try      {

                           e.mythrow();

                    }        catch(...)     {

                    }

            }

            大家看看結(jié)果就知道了,throw后,調(diào)用當(dāng)前類的析構(gòu),整個(gè)結(jié)束了這個(gè)類的歷史使命。唉~~

            3.         異常規(guī)格說明

            如果我們調(diào)用別人的函數(shù),里面有異常拋出,我用去查看它的源代碼去看看都有什么異常拋出嗎?可以,但是太——煩躁。比較好的解決辦法,是編寫帶有異常拋出的函數(shù)時(shí),采用異常規(guī)格說明,使我們看到函數(shù)聲明就知道有哪些異常出現(xiàn)。

            異常規(guī)格說明大體上為以下格式:

            void ExceptionFunction(argument…) throw(ExceptionClass1, ExceptionClass2, ….)

            對了,所有異常類都在函數(shù)末尾的throw()的括號中得以說明了,這樣,對于函數(shù)調(diào)用者來說,是一清二楚了!

            注意下面一種形式:

            void ExceptionFunction(argument…) throw()

            表明沒有任何異常拋出

            而正常的void ExceptionFunction(argument…)則表示:可能拋出任何一種異常,當(dāng)然就,也可能沒有異常,意義是最廣泛的哦。

            4.         構(gòu)造和析構(gòu)中的異常拋出

            55555,到了應(yīng)該注意的地方了。

                    先看個(gè)程序,假如我在構(gòu)造函數(shù)的地方拋出異常,這個(gè)類的析構(gòu)會被調(diào)用嗎?可如果不調(diào)用,那類里的東西豈不是不能被釋放了??

                    程序:

            #include <iostream.h>

            #include <stdlib.h>

            class ExceptionClass1{

                    char* s;

            public:

                    ExceptionClass1(){

                           cout<<"ExceptionClass1()"<<endl;

                           s=new char[4];

                           cout<<"throw a exception"<<endl;

                           throw 18;

                    }

                    ~ExceptionClass1(){

                           cout<<"~ExceptionClass1()"<<endl;

                           delete[] s;

                    }

            };

            void main(){

                    try{

                           ExceptionClass1 e;

                    }catch(...)

                    {}

            }

            結(jié)果為:

            ExceptionClass1()

            throw a exception

            沒了,沒了,到此為止了!可是,可是,在這兩句輸出之間,我們已經(jīng)給S分配了內(nèi)存,哪里去了?內(nèi)存釋放了嗎?沒有,沒有,因?yàn)樗窃?strong style="color: white; background-color: #00aa00;">析構(gòu)函數(shù)中釋放的,哇!問題大了去了。怎么辦?怎么辦?

            為了避免這種情況,應(yīng)避免對象通過本身的構(gòu)造函數(shù)涉及到異常拋出。即:既不在構(gòu)造函數(shù)中出現(xiàn)異常拋出,也不應(yīng)在構(gòu)造函數(shù)調(diào)用的一切東西中出現(xiàn)異常拋出。否則,只有完蛋。

            那么,在析構(gòu)函數(shù)中的情況呢?我們已經(jīng)知道,異常拋出之后,就要調(diào)用本身的析構(gòu)函數(shù),如果這析構(gòu)函數(shù)中還有異常拋出的話,則已存在的異常尚未被捕獲,會導(dǎo)致異常捕捉不到哩。

            完,也就是說,我們不要在構(gòu)造函數(shù)和析構(gòu)函數(shù)中存在異常拋出

            5.         異常捕獲

            上邊的程序不知道大家看懂了沒,異常捕獲已經(jīng)在上面出現(xiàn)了也。

            沒錯(cuò),就是try{…}catch(…){…}這樣的結(jié)構(gòu)!

            Try后面的花括號中,就是有可能涉及到異常的各種聲明啊調(diào)用啊之類的,如果有異常拋出,就會被異常處理器截獲捕捉到,轉(zhuǎn)給catch處理。先把異常的類和catch后面小括號中的類進(jìn)行比較,如果一致,就轉(zhuǎn)到后面的花括號中進(jìn)行處理。

            例如拋出異常是這么寫的:

            void f(){throw ExceptionClass(“ya, J”);}

            假設(shè)類ExceptionClass有個(gè)成員函數(shù)function()在有異常時(shí)進(jìn)行處理或相應(yīng)的消息顯示(只是做個(gè)例子哦,別挑我的刺兒)。

            那么,我可以這么捕捉: try{f()}catch(ExceptionClass e){e.function()};

            當(dāng)然,象在上面程序中出現(xiàn)的一樣,我可以在catch后用三個(gè)點(diǎn)來代表所有異常。如try{f()}catch(…){}。這樣就截?cái)嗔怂谐霈F(xiàn)的異常。有助于把所有沒出現(xiàn)處理的異常屏蔽掉(我是這么認(rèn)為的J)。

            異常捕獲之后,我可以再次拋出,就用一個(gè)不帶任何參數(shù)的throw語句就可以了,例如:try(f())catch(…){throw}

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

            正象許多人想象的一樣,C++肯定有自己的標(biāo)準(zhǔn)的異常類。

            一個(gè)總基類:

            exception               是所有C++異常的基類。

            下面派生了兩個(gè)異常類:

            logic_erro               報(bào)告程序的邏輯錯(cuò)誤,可在程序執(zhí)行前被檢測到。

            runtime_erro          顧名思義,報(bào)告程序運(yùn)行時(shí)的錯(cuò)誤,只有在運(yùn)行的時(shí)候才能檢測到。

            以上兩個(gè)又分別有自己的派生類:

            由logic_erro派生的異常類

            domain_error                 報(bào)告違反了前置條件

            invalid_argument            指出函數(shù)的一個(gè)無效參數(shù)

            length_error 指出有一個(gè)產(chǎn)生超過NPOS長度的對象的企圖(NPOS為size_t的最大可表現(xiàn)值

            out_of_range 報(bào)告參數(shù)越界

            bad_cast                       在運(yùn)行時(shí)類型識別中有一個(gè)無效的dynamic_cast表達(dá)式

            bad_typeid 報(bào)告在表達(dá)式typeid(*p)中有一個(gè)空指針P

            由runtime_error派生的異常

            range_error 報(bào)告違反了后置條件

            overflow_error 報(bào)告一個(gè)算術(shù)溢出

            bad_alloc                      報(bào)告一個(gè)存儲分配錯(cuò)誤

            呼呼,這是我這兩天研究異常的總結(jié)報(bào)告。呼呼,累。

            C++編譯器如何實(shí)現(xiàn)異常處理1 -- 摘自互聯(lián)網(wǎng)
                與傳統(tǒng)語言相比,C++的一 項(xiàng)革命性創(chuàng)新就是它支持異常處理。傳統(tǒng)的錯(cuò)誤處理方式經(jīng)常滿足不了要求,而異常處理則是一個(gè)極好的替代解決方案。它將正常 代碼和錯(cuò)誤處理代碼清晰的劃分開來,程序變得非常干凈并且容易維護(hù)。本文討論了編譯器如何實(shí)現(xiàn)異常處理。我將假定你已經(jīng)熟悉異常處理的語法和機(jī)制。本文還 提供了一個(gè)用于VC++的異常處理庫,要用庫中的處理程序替換掉VC++提供的那個(gè),你只需要調(diào)用下面這個(gè)函數(shù):

            install_my_handler();

            之后,程序中的所有異常,從它們被拋出到堆棧展開(stack unwinding),再到調(diào)用catch塊,最后到程序恢復(fù)正常運(yùn)行,都將由我的異常處理庫來管理。

            與其它C++特性一樣,C++標(biāo)準(zhǔn)并沒有規(guī)定編譯器應(yīng)該如何來實(shí)現(xiàn)異常處理。這意味著每一個(gè)編譯器的提供商都可以用它們認(rèn)為恰當(dāng)?shù)姆绞絹韺?shí)現(xiàn)它。下面我 會描述一下VC++是怎么做的,但即使你使用其它的編譯器或操作系統(tǒng)①,本文也應(yīng)該會是一篇很好的學(xué)習(xí)材料。VC++的實(shí)現(xiàn)方式是以windows系統(tǒng)的 結(jié)構(gòu)化異常處理(SEH)②為基礎(chǔ)的。

            結(jié)構(gòu)化異常處理—概述

            在本文的討論中,我認(rèn)為異常或者是被明確的拋出的, 或者是 由于除零溢出、空指針訪問等引起的。當(dāng)它發(fā)生時(shí)會產(chǎn)生一個(gè)中斷,接下來控制權(quán)就會傳遞到操作系統(tǒng)的手中。操作系統(tǒng)將調(diào)用異常處理程序,檢查從異常發(fā)生位置 開始的函數(shù)調(diào)用序列,進(jìn)行堆棧展開和控制權(quán)轉(zhuǎn)移。Windows定義了結(jié)構(gòu)“EXCEPTION_REGISTRATION”,使我們能夠向操作系統(tǒng)注冊 自己的異常處理程序。

            struct EXCEPTION_REGISTRATION
            {
            EXCEPTION_REGISTRATION* prev;
            DWORD handler;
            };

            注冊時(shí),只需要創(chuàng)建這樣一個(gè)結(jié)構(gòu),然后把它的地址放到FS段偏移0的位置上去就行了。下面這句匯編代碼演示了這一操作:
            mov FS:[0], exc_regp

            prev字段用于建立一個(gè)EXCEPTION_REGISTRATION結(jié)構(gòu)的鏈表,每次注冊新的EXCEPTION_REGISTRATION時(shí),我們都要把原來注冊的那個(gè)的地址存到prev中。

            那么,那個(gè)異常回調(diào)函數(shù)長什么樣呢?在excpt.h中,windows定義了它的原形:

            EXCEPTION_DISPOSITION (*handler)(
            _EXCEPTION_RECORD *ExcRecord,
            void* EstablisherFrame,
            _CONTEXT *ContextRecord,
            void* DispatcherContext);

            不要管它的參數(shù)和返回值,我們先來看一個(gè)簡單的例子。下面的程序注冊了一個(gè)異常處理程序,然后通過除以零產(chǎn)生了一個(gè)異常。異常處理程序捕獲了它,打印了一條消息就完事大吉并退出了。

            #include <iostream>
            #include <windows.h>

            using std::cout;
            using std::endl;

            struct EXCEPTION_REGISTRATION
            {
            EXCEPTION_REGISTRATION* prev;
            DWORD handler;
            };

            EXCEPTION_DISPOSITION myHandler(
            _EXCEPTION_RECORD *ExcRecord,
            void * EstablisherFrame,
            _CONTEXT *ContextRecord,
            void * DispatcherContext)
            {
            cout << "In the exception handler" << endl;
            cout << "Just a demo. exiting..." << endl;
            exit(0);
            return ExceptionContinueExecution; //不會運(yùn)行到這
            }

            int g_div = 0;

            void bar()
            {
            //初始化一個(gè)EXCEPTION_REGISTRATION結(jié)構(gòu)
            EXCEPTION_REGISTRATION reg, *preg = ?
            reg.handler = (DWORD)myHandler;

            //取得當(dāng)前異常處理鏈的“頭”
            DWORD prev;
            _asm
            {
            mov EAX, FS:[0]
            mov prev, EAX
            }
            reg.prev = (EXCEPTION_REGISTRATION*) prev;

            //注冊!
            _asm
            {
            mov EAX, preg
            mov FS:[0], EAX
            }

            //產(chǎn)生一個(gè)異常
            int j = 10 / g_div; //異常,除零溢出
            }

            int main()
            {
            bar();
            return 0;
            }

            /*-------輸出-------------------
            In the exception handler
            Just a demo. exiting...
            ---------------------------------*/

            注意EXCEPTION_REGISTRATION必須定義在棧上,并且必須位于比上一個(gè)結(jié)點(diǎn)更低的內(nèi)存地址上,Windows對此有嚴(yán)格要求,達(dá)不到的話,它就會立刻終止進(jìn)程。
            函數(shù)和堆棧

            堆棧是用來保存局部對象的連續(xù)內(nèi)存區(qū)。更明確的說,每個(gè)函數(shù)都有一個(gè)相關(guān)的棧楨(stack frame)來保存它所有的局部對象和表達(dá)式計(jì)算過程中用到的臨時(shí)對象,至少理論上是這樣的。但現(xiàn)實(shí)中,編譯器經(jīng)常會把一些對象放到寄存器中以便能以更快 的速度訪問。堆棧是一個(gè)處理器(CPU)層次的概念,為了操縱它,處理器提供了一些專用的寄存器和指令。

            圖1是一個(gè)典型的堆棧,它示出了函數(shù)foo調(diào)用bar,bar又調(diào)用widget時(shí)的情景。請注意堆棧是向下增長的,這意味著新壓入的項(xiàng)的地址低于原有項(xiàng)的地址。



            通常編譯器使用EBP寄存器來指示當(dāng)前活動的棧楨。本例中,CPU正在運(yùn)行widget,所以圖中的EBP指向了widget的棧楨。編譯器在編譯時(shí)將 所有局部對象解析成相對于棧楨指針(EBP)的固定偏移,函數(shù)則通過棧楨指針來間接訪問局部對象。舉個(gè)例子,典型的,widget訪問它的局部變量時(shí)就是 通過訪問棧楨指針以下的、有著確定位置的幾個(gè)字節(jié)來實(shí)現(xiàn)的,比如說EBP-24。

            上圖中也畫出了ESP寄存器,它叫棧指針,指向棧的最后一項(xiàng)。在本例中,ESP指著widget的棧楨的末尾,這也是下一個(gè)棧楨(如果它被創(chuàng)建的話)的開始位置。

            處理器支持兩種類型的棧操作:壓棧(push)和彈棧(pop)。比如,pop EAX的作用是從ESP所指的位置讀出4字節(jié)放到EAX寄存器中,并把ESP加上(記住,棧是向下增長的)4(在32位處理器上);類似的,push EBP的作用是把ESP減去4,然后將EBP的值放到ESP指向的位置中去。

            編譯器編譯一個(gè)函數(shù)時(shí),會在它的開頭添加一些代碼來為其創(chuàng)建并初始化棧楨,這些代碼被稱為序言(prologue);同樣,它也會在函數(shù)的結(jié)尾處放上代碼來清除棧楨,這些代碼叫做尾聲(epilogue)。

            一般情況下,序言是這樣的:

            Push EBP ; 把原來的棧楨指針保存到棧上
            Mov EBP, ESP ; 激活新的棧楨
            Sub ESP, 10 ; 減去一個(gè)數(shù)字,讓ESP指向棧楨的末尾

            第一條指令把原來的棧楨指針EBP保存到棧上;第二條指令通過讓EBP指向主調(diào)函數(shù)的EBP的保存位置來激活被調(diào)函數(shù)的棧楨;第三條指令把ESP減去了 一個(gè)數(shù)字,這樣ESP就指向了當(dāng)前棧楨的末尾,而這個(gè)數(shù)字是函數(shù)要用到的所有局部對象和臨時(shí)對象的大小。編譯時(shí),編譯器知道函數(shù)的所有局部對象的類型和 “體積”,所以,它能很容易的計(jì)算出棧楨的大小。

            尾聲所做的正好和序言相反,它必須把當(dāng)前棧楨從棧上清除掉:

            Mov ESP, EBP
            Pop EBP ; 激活主調(diào)函數(shù)的棧楨
            Ret ; 返回主調(diào)函數(shù)

            它讓ESP指向主調(diào)函數(shù)的棧楨指針的保存位置(也就是被調(diào)函數(shù)的棧楨指針指向的位置),彈出EBP從而激活主調(diào)函數(shù)的棧楨,然后返回主調(diào)函數(shù)。

            一旦CPU遇到返回指令,它就要做以下兩件事:把返回地址從棧中彈出,然后跳轉(zhuǎn)到那個(gè)地址去。返回地址是主調(diào)函數(shù)執(zhí)行call指令調(diào)用被調(diào)函數(shù)時(shí)自動壓 棧的。Call指令執(zhí)行時(shí),會先把緊隨在它后面的那條指令的地址(被調(diào)函數(shù)的返回地址)壓入棧中,然后跳轉(zhuǎn)到被調(diào)函數(shù)的開始位置。圖2更詳細(xì)的描繪了運(yùn)行 時(shí)的堆棧。如圖所示,主調(diào)函數(shù)把被調(diào)函數(shù)的參數(shù)也壓進(jìn)了堆棧,所以參數(shù)也是棧楨的一部分。函數(shù)返回后,主調(diào)函數(shù)需要移除這些參數(shù),它通過把所有參數(shù)的總體 積加到ESP上來達(dá)到目的,而這個(gè)體積可以在編譯時(shí)知道:

            Add ESP, args_size

            當(dāng)然,也可以把參數(shù)的總體積寫在被調(diào)函數(shù)的返回指令的后面,讓被調(diào)函數(shù)去移除參數(shù),下面的指令就在返回主調(diào)函數(shù)前從棧中移去了24個(gè)字節(jié):

            Ret 24

            取決于被調(diào)函數(shù)的調(diào)用約定(call convention),這兩種方式每次只能用一個(gè)。你還要注意的是每個(gè)線程都有自己獨(dú)立的堆棧。

              C++和異常

            回憶一下我在第一節(jié)中介紹的EXCEPTION_REGISTRATION結(jié)構(gòu),我們曾用它向操作系統(tǒng)注冊了發(fā)生異常時(shí)要被調(diào)用的回調(diào)函數(shù)。VC++也是這么做的,不過它擴(kuò)展了這個(gè)結(jié)構(gòu)的語義,在它的后面添加了兩個(gè)新字段:
            struct EXCEPTION_REGISTRATION
            {
            EXCEPTION_REGISTRATION* prev;
            DWORD handler;
            int id;
            DWORD ebp;
            };

            VC++會為絕大部分函數(shù)③添加一個(gè)EXCEPTION_REGISTRATION類型的局部變量,它的最后一個(gè)字段(ebp)與棧楨指針指向的位置重 疊。函數(shù)的序言創(chuàng)建這個(gè)結(jié)構(gòu)并把它注冊給操作系統(tǒng),尾聲則恢復(fù)主調(diào)函數(shù)的EXCEPTION_REGISTRATION。id字段的意義我將在下一節(jié)介 紹。

            VC++編譯函數(shù)時(shí)會為它生成兩部分?jǐn)?shù)據(jù):

            a)異常回調(diào)函數(shù)

            b)一個(gè)包含函數(shù)重要信息的數(shù)據(jù)結(jié)構(gòu),這些信息包括catch塊、這些塊的地址和這些塊所關(guān)心的異常的類型等等。我把這個(gè)結(jié)構(gòu)稱為funcinfo,有關(guān)它的詳細(xì)討論也在下一節(jié)。

            圖3是考慮了異常處理之后的運(yùn)行時(shí)堆棧。widget的異常回調(diào)函數(shù)位于由FS:[0]指向的異常處理鏈的開始位置(這是由widget的序言設(shè)置 的)。異常處理程序把widget的funcinfo結(jié)構(gòu)的地址交給函數(shù)__CxxFrameHandler,__CxxFrameHandler會檢查 這個(gè)結(jié)構(gòu)看函數(shù)中有沒有catch塊對當(dāng)前的異常感興趣。如果沒有的話,它就返回ExceptionContinueSearch給操作系統(tǒng),于是操作系 統(tǒng)會從異常處理鏈表中取得下一個(gè)結(jié)點(diǎn),并調(diào)用它的異常處理程序(也就是調(diào)用當(dāng)前函數(shù)的那個(gè)函數(shù)的異常處理程序)。

            這一過程將一直進(jìn)行下去——直到處理程序找到一個(gè)能處理當(dāng)前異常的catch塊為止,這時(shí)它就不再返回操作系統(tǒng)了。但是在調(diào)用catch塊之前(由于有 funcinfo結(jié)構(gòu),所以知道catch塊的入口,參見圖3),必須進(jìn)行堆棧展開,也就是清理掉當(dāng)前函數(shù)的棧楨下面的所有其他的棧楨。這個(gè)操作稍微有點(diǎn) 復(fù)雜,因?yàn)椋寒惓L幚沓绦虮仨氄业疆惓0l(fā)生時(shí)生存在這些棧楨上的所有局部對象,并依次調(diào)用它們的析構(gòu)函數(shù)。后面我將對此進(jìn)行詳細(xì)介紹。

            異常處理程序把這項(xiàng)工作委托給了各個(gè)棧楨自己的異常處理程序。從FS:[0]指向的異常處理鏈的第一個(gè)結(jié)點(diǎn)開始,它 依次調(diào)用每個(gè)結(jié)點(diǎn)的處理程序,告訴它堆棧正在展開。與之相呼應(yīng),這些處理程序會調(diào)用每個(gè)局部對象的析構(gòu)函數(shù),然后返回。此過程一直進(jìn)行到與異常處理程序自 身相對應(yīng)的那個(gè)結(jié)點(diǎn)為止。

            由于catch塊是函數(shù)的一部分,所以它使用的也是函數(shù)的棧楨。因此,在調(diào)用catch塊之前,異常處理程序必須激活它所隸屬的函數(shù)的棧楨。

            其次,每個(gè)catch塊都只接受一個(gè)參數(shù),其類型是它希望捕獲的異常的類型。異常處理程序必須把異常對象本身或者是異常對象的引用拷貝到catch塊的棧 楨上,編譯器在funcinfo中記錄了相關(guān)信息,處理程序根據(jù)這些信息就能知道到哪去拷貝異常對象了。

            拷貝完異常并激活棧楨后,處理程序?qū)⒄{(diào)用catch塊。而catch塊將把控制權(quán)下一步要轉(zhuǎn)移到的地址返回來。請注意:雖然這時(shí)堆棧已經(jīng)展開,棧楨也都 被清除了,但它們占據(jù)的內(nèi)存空間并沒有被覆蓋,所有的數(shù)據(jù)都還好好的待在棧上。這是因?yàn)楫惓L幚沓绦蛉栽趫?zhí)行,象其他函數(shù)一樣,它也需要棧來存放自己的局 部對象,而其棧楨就位于發(fā)生異常的那個(gè)函數(shù)的棧楨的下面。catch塊返回以后,異常處理程序需要“殺掉”異常對象。此后,它讓ESP指向目標(biāo)函數(shù)(控制 權(quán)要轉(zhuǎn)移到的那個(gè)函數(shù))的棧楨的末尾——這樣就把(包括它自己的在內(nèi)的)所有棧楨都刪除了,然后再跳轉(zhuǎn)到catch塊返回的那個(gè)地址去,就勝利的完成整個(gè) 異常處理任務(wù)了。但它怎么知道目標(biāo)函數(shù)的棧楨末尾在哪呢?事實(shí)上它沒法知道,所以編譯器把這個(gè)地址保存到了棧楨上(由前言來完成),如圖3所示,棧楨指針 EBP下面第16個(gè)字節(jié)就是。

            當(dāng)然,catch塊也可能拋出新異常,或者是將原來的異常重新拋出。處理程序必須對此有所準(zhǔn)備。如果是拋出新異常,它必須殺掉原來的那個(gè);而如果是重新拋出原來的異常,它必須能繼續(xù)傳播(propagate)這個(gè)異常。

            這里我要特別強(qiáng)調(diào)一點(diǎn):由于每個(gè)線程有自己獨(dú)立的堆棧,所以每個(gè)線程也都有自己獨(dú)立的、由FS:[0]指向的EXCEPTION_REGISTRATION鏈。

            posted on 2008-11-07 22:41 大龍 閱讀(375) 評論(0)  編輯 收藏 引用


            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            久久久久亚洲精品无码网址 | 亚洲&#228;v永久无码精品天堂久久| 亚洲?V乱码久久精品蜜桃| 91精品国产91热久久久久福利 | 狠狠色丁香婷婷综合久久来来去| 久久99国产精品尤物| 亚洲欧美成人综合久久久| 亚洲精品国精品久久99热一| 久久久久免费精品国产| 亚洲午夜久久久久妓女影院| 少妇高潮惨叫久久久久久| 亚洲精品乱码久久久久久蜜桃不卡 | 无码人妻久久一区二区三区蜜桃 | 国产成人久久精品一区二区三区| 亚洲va久久久噜噜噜久久| 久久精品中文闷骚内射| 久久最近最新中文字幕大全| 久久国产成人精品国产成人亚洲| 欧美性大战久久久久久| 亚洲熟妇无码另类久久久| 91精品国产色综合久久| 日本精品久久久久中文字幕8| 国产精品美女久久久久AV福利| 蜜臀久久99精品久久久久久| 久久无码中文字幕东京热| 青青草原精品99久久精品66| 日本精品久久久久中文字幕8| 欧美日韩精品久久久免费观看| 精品国产乱码久久久久软件| 国产一区二区三区久久精品| 久久青青草原精品国产不卡| 色欲久久久天天天综合网精品| 日韩精品久久久久久| 久久久久亚洲AV无码专区首JN| 久久久久久综合一区中文字幕| 久久综合伊人77777麻豆| 72种姿势欧美久久久久大黄蕉 | AAA级久久久精品无码片| 无码任你躁久久久久久久| 久久成人精品视频| 99久久精品国产一区二区|