一個異常被拋出時,就會立即引發(fā) C++ 的異常捕獲機制:
 圖4 C++ 異常捕獲機制
在上一小節(jié)中,我們已經(jīng)看到了 nStep 變量在跟蹤對象構(gòu)造、析構(gòu)方面的作用。實際上 nStep 除了能夠跟蹤對象創(chuàng)建、銷毀階段以外,還能夠標識當前執(zhí)行點是否在 try 塊中,以及(如果當前函數(shù)有多個 try 塊的話)究竟在哪個 try 塊中。這是通過在每一個 try 塊的入口和出口各為 nStep 賦予一個唯一 ID 值,并確保 nStep 在對應(yīng) try 塊內(nèi)的變化恰在此范圍之內(nèi)來實現(xiàn)的。
在具體實現(xiàn)異常捕獲時,首先,C++ 異常處理器檢查發(fā)生異常的位置是否在當前函數(shù)的某個 try 塊之內(nèi)。這項工作可以通過將當前函數(shù)的 nStep 值依次在 piHandler 指向 tblTryBlocks[] 表的條目中進行范圍為 [nBeginStep, nEndStep) 的比對來完成。
例如:若圖4 中的 FuncB 在 nStep == 2 時發(fā)生了異常,則通過比對 FuncB 的 tblTryBlocks[] 表發(fā)現(xiàn) 2∈[1, 3),故該異常發(fā)生在 FuncB 內(nèi)的第一個 try 塊中。
其次,如果異常發(fā)生的位置在當前函數(shù)中的某個 try 塊內(nèi),則嘗試匹配該 tblTryBlocks[] 相應(yīng)條目中的 tblCatchBlocks[] 表。tblCatchBlocks[] 表中記錄了與指定 try 塊配套出現(xiàn)的所有 catch 塊相關(guān)信息,包括這個 catch 塊所能捕獲的異常類型及其起始地址等信息。
若找到了一個匹配的 catch 塊,則復(fù)制當前異常對象到此 catch 塊,然后跳轉(zhuǎn)到其入口地址執(zhí)行塊內(nèi)代碼。
否則,則說明異常發(fā)生位置不在當前函數(shù)的 try 塊內(nèi),或者這個 try 塊中沒有與當前異常相匹配的 catch 塊,此時則沿著函數(shù)棧框架中 piPrev 所指地址(即:異常處理鏈中的上一個節(jié)點)逐級重復(fù)以上過程,直至找到一個匹配的 catch 塊或到達異常處理鏈的首節(jié)點。對于后者,我們稱為發(fā)生了未捕獲的異常,對于 C++ 異常處理器而言,未捕獲的異常是一個嚴重錯誤,將導(dǎo)致當前進程被強制結(jié)束。
注意:雖然在圖4示例中的 tblTryBlocks[] 只有一個條目,這個條目中的 tblCatchBlocks[] 也只有一行。但是在實際情況中,這兩個表中都允許有多條記錄。意即:一個函數(shù)中可以有多個 try 塊,每個 try 塊后均可跟隨多個與之配套的 catch 塊。
注意:按照標準意義上的理解,異常時的棧回退是伴隨著異常捕獲過程沿著異常處理鏈逐層向上進行的。但是有些編譯器是在先完成異常捕獲后再一次性進行棧回退的。無論具體實現(xiàn)使用了哪種方式,除非正在開發(fā)一個內(nèi)存嚴格受限的嵌入式應(yīng)用,通常我們按照標準語意來理解都不會產(chǎn)生什么問題。
備注:實際上 tblCatchBlocks 中還有一些較為關(guān)鍵但被故意省略的字段。比如指明該 catch 塊異常對象復(fù)制方式(傳值(拷貝構(gòu)造)或傳址(引用或指針))的字段,以及在何處存放被復(fù)制的異常對象(相對于入口地址的偏移位置)等信息。 |