C 異常機制

2021-06-16 20:36:33 字數 3988 閱讀 4341

在c++中的函式呼叫中,是用棧來存放其中資料物件。

表1.1

我們結合這張表,來簡單介紹函式的棧結構。

其中每乙個函式在入棧的時候,編譯器會自動新增額外的資料結構,這裡的exception_registration就是被額外新增進來的。對於這個結構體我們稍後解釋,首先來介紹函式的基本結構。

從這張圖中可以清楚的看到,在函式的棧結構中,會存放的是函式的引數和區域性變數,這些物件在函式結束的時候都會被析構掉,在之後會介紹這乙個清理的過程。

我們還注意到每乙個函式都有乙個esp,這個esp可以被認為乙個分界,下乙個函式可以這之後開始存放它的資料物件。(這裡是棧,我們的順序是從下至上)

接下我們來介紹exception_registration這個結構體

表1.2

第乙個成員中有乙個ebp。這個ebp是用來標識當前的棧活動幀,簡單來說就是指明當前我們在執行哪乙個函式。

所以caller』s ebp可以幫我們去找到呼叫函式。

第二個成員id表明函式的執行狀態和進度。這乙個id在棧回退和異常捕獲中都扮演至關重要的角色,我會在之後聯絡c++的棧回退機制進行詳細的介紹。

第三個成員handler,它的工作便是將當前函式的資訊funinfo傳給__cxxframehandler

,然後由它全權處理異常。處理的過程會在之後做介紹。

第四個成員prev,可以通過prev找到上一層的異常處理,比如這裡fun2的exception_registration中的prev便是指向fun1的exception_registration

這裡還有乙個fs:[0]。exception_registration中的prev將棧中的exception_registration連成了乙個鍊錶,這裡我就暫時就叫它異常處理鏈。而fs:[0]就儲存了這個鍊錶的第乙個節點。當乙個異常丟擲的時候,便可以通過它來找到第乙個異常處理。

throw exception();
上面這個丟擲異常的語句。編譯器會對它做一定的處理,變成如下:

e e = e(); //create exception on the stack

_cxxthrowexception(&e, e_excpt_info_addr);

其中主要是將異常物件e傳給_cxxthrowexception,並儲存待丟擲異常物件的起始位址、用於銷毀它的析構函式,以及它的 type_info 資訊。

然後通過fs:[0]呼叫到了異常處理exception_registration中的hanlder進行了處理。

首先來介紹乙個非常重要的機制,棧回退機制

先來看一下乙個具體的例子。

void fun(bool b)

classb obj4;

…… }

catch(cexception* e)

}

這裡編譯器在編譯這段**的時候,會做一定的處理,下面便是處理過後的**

void fun(bool b)

classb obj4;

obj4.classb(); id = 6; ……

obj4.~classb();

id = 7; //try**段結束

}catch(cexception* e)

}

在每乙個函式中都會有乙個回退表,裡面存放了如何幫助棧回退的資訊

我們把上面的函式的回退表大致列出來

表3.1

對於這張表稍作解釋

第一列id便是函式中執行到了第幾步;

第二列inextid是為了執行完**中當前行析構後,找到下一行的執行行;

第三列pfundestory存放了析構函式指標,通過第四列的pobj機上ebp便可以交由析構函式處理。

當然如果在執行到id = 6後丟擲了異常,那麼當前的id便是6 我們就去回退表中找id = 6的行,開始執行。一直執行到inextid = -1 停止。

如果乙個建構函式丟擲了異常了,那麼按照c++的異常機制是不會執行到它的析構函式的。就以上面這個例子,比如obj2在構造的時候丟擲了異常了,那麼按照前面討論的,異常處理機制清理物件,那麼這個時候id還只是2,按照上面的unwind_table中的順序,析構完obj1後便會結束了,obj2就永遠不會析構掉。

我們會向第一節講到函式的棧結構中,有提到乙個funinfo這個結構中儲存了當前函式的資訊。

表4.1 

其中unwind_table便是上一節講到的回退表。tryblock_table便是這裡異常捕獲的關鍵。

表4.2 

這個結構便是tryblock_table。

這裡start_id 便是try**段開始,在上一節的例子中start_id便是1;而end_id便是7。

圖4.1

結合圖4.1,我們來了解這個流程。當丟擲異常的時候,系統會將異常傳給_cxxthrowexception

(回憶第二節中的異常丟擲介紹)。

接著系統通過fs:[0]找到異常鏈的頭,從而獲得exception_registration中的id(聯絡第三節中棧回退機制,這個id標識當前函式執行到了第幾步)。

然後在當前函式的funinfo中的tryblock_table中判斷id是否落在了start_id和end_id之間(包括id = start_id 和 id = end_id這兩種情況):

(1)如果id落在了try的範圍內:

這個時候我們就要去判斷,當前我們丟擲的異常和catch中的引數是否乙個型別的。

這裡我們會用到c++的乙個rtti(run time type identification)機制,這樣就可以進行判斷了:

如果丟擲異常符合catch的要求,就進入catch,進行異常處理,包括函式物件的清理;如果丟擲異常不符合catch的要求,就轉向步驟(3)

(2)如果id不在try範圍內,便轉向步驟(3)

(3)返回exceptioncontinuesearch,我們就會通過exception_registration中的prev找到上一層的異常處理,交由它處理,轉向步驟(1)。

上面的過程將會一直繼續,直到丟擲異常被catch住,或者系統已經走到了異常鏈的尾端。

上述的操作在__cxxframehandler中執行,回憶一下,第一節中介紹的exception_registration中handler,它做得處理便是將函式的資訊funinfo交給了__cxxframehandler

how a c++ compiler implements exception handling

c++異常機制的實現方式和開銷分析

C 異常 異常機制

c 異常是丟程式執行過程中發生的異常情況 例如被0除 的一種響應。異常提供了將控制權從程式的乙個部分傳遞到另一部分的途徑。對異常的處理有3個組成部分 引發異常 使用處理程式捕獲異常 使用try塊。程式在出現問題時將引發異常。throw語句實際上是跳轉,即命令程式跳到另一條語句。throw關鍵字表示引...

C 異常機制

一 簡單例項 異常是通過丟擲物件而引發的,丟擲物件的型別決定應該啟用那個處理 即catch中的語句 被處理 是呼叫鏈中與該丟擲物件型別匹配,並且離丟擲異常位置最近的那乙個 例如 void test int main catch string e t value void print void pus...

C 異常機制

1 異常的丟擲 classmyerror voiddo error intmain 上面的例子中,通過throw丟擲了乙個異常類的例項,這個異常類,可以是任何乙個自定義的類,通過例項化傳入的引數可以表明發生的錯誤資訊。其實異常就是乙個帶有異常資訊的類而已。異常被丟擲後,需要 獲,從而可以從錯誤中進行...