1、函數調用鏈
函數與子函數之間的調用關係可以用一個鏈來表示,調用連,相關的一個重要資料結構就是棧幀,子函數返回的時候,棧展開,構造在棧上的對象被編譯器設定的解構函式析構銷毀。
函數調用 A ----------------------> B ---------------------> C ------------------------->D
2、異常處理鏈
類似的,windows上的結構化異常處理也有一個類似的異常處理鏈,而且這個鏈剛好也是在棧上構造的,但是和函數正常的鏈是各自獨立的。當某一個子函數發生異常的時候,就不能走正常的調用連返回,進而析構對象;這個時候,需要走異常鏈來回溯,展開異常調用鏈,所有附著在異常鏈上的對象,在異常鏈的第二次調用(unwind處理,第一次是搜尋異常處理者)的時候析構。
異常鏈 ExcpNode_A-------> ExcpNode_B------------------------------------------->!!BOMB!!<
每一個節點都包含了該函數對應的對象的析構操作,正常調用鏈也好,異常鏈也好,都是這樣的,這些是由C++編譯器完成的。每一個call操作,c++編譯器產生一個子函數棧幀節點。每一個try-catch語句,c++語句構造一個異常鏈節點。
但是,如果一個程式員在C函數裡面沒有寫try-catch,但是D函數裡面有try-catch語句,而且啟動並執行時候,在D函數裡面發生了異常,那麼C函數裡面的產生的所有對象得不到析構的機會。
3、測試程式碼
// debugee.cpp : Defines the entry point for the console application.//#include "stdafx.h"class Object{public:Object(const char tag) : m_tag(tag){printf("Object [%c] %p\n", m_tag, this);};~Object(){printf("~Object [%c] %p\n", m_tag, this);};private:char m_tag;};void D(void){Object d('D');*(int *)0 = 0;}void C(void){Object c('C');D();}void B(void){Object b('B');try{C();}catch(...){printf("B caught exception.\n");}}void A(void){Object a('A');try{B();}catch(...){printf("A caught exception.\n");}}int main(int argc, char* argv[]){A();return 0;}
4、輸出:
5、注意編譯器實現差異
vc編譯器的版本差異和編譯選項的差異,vc2005的/EHsc標記。
class Object{public: Object(const char tag) : m_tag(tag) {printf("Object [%c] %p\n", m_tag, this);}; ~Object() {printf("~Object [%c] %p\n", m_tag, this);};private: char m_tag;};void E(void){ Object e('E'); //try { throw(""); } //catch (...) { }}void D(void){ Object d('D'); E();}void C(void){ Object c('C'); D();}void B(void){ Object b('B'); //try { C(); } //catch(...) { printf("B caught exception.\n"); }}void A(void){ Object a('A'); try { B(); }catch(...) { printf("A caught exception.\n"); }}int main(int argc, char* argv[]){ A(); return 0;}
不帶/EHsc產生的結果符合預期