天天看點

棧展開(stack unwinding)

棧展開(stack unwinding)的定義

抛出異常時,将暫停目前函數的執行,開始查找比對的

catch

子句。首先檢查

throw

本身是否在

try

塊内部,如果是,檢查與該

try

相關的

catch

子句,看是否可以處理該異常。如果不能處理,就退出目前函數,并且釋放目前函數的記憶體并銷毀局部對象,繼續到上層的調用函數中查找,直到找到一個可以處理該異常的

catch

。這個過程稱為棧展開(stack unwinding)。當處理該異常的

catch

結束之後,緊接着該

catch

之後的點繼續執行。

  1. 為局部對象調用析構函數

    在棧展開的過程中,會釋放局部對象所占用的記憶體并運作類類型局部對象的析構函數。但需要注意的是,如果一個塊通過

    new

    動态配置設定記憶體,并且在釋放該資源之前發生異常,該塊因異常而退出,那麼在棧展開期間不會釋放該資源,編譯器不會删除該指針,這樣就會造成記憶體洩露。
  2. 析構函數應該從不抛出異常

    在為某個異常進行棧展開的時候,析構函數如果又抛出自己的未經處理的另一個異常,将會導緻調用标準庫

    terminate

    函數。通常

    terminate

    函數将調用

    abort

    函數,導緻程式的非正常退出。是以析構函數應該從不抛出異常。
  3. 異常與構造函數

    如果在構造函數對象時發生異常,此時該對象可能隻是被部分構造,要保證能夠适當的撤銷這些已構造的成員。

  4. 未捕獲的異常将會終止程式

    不能不處理異常。如果找不到比對的catch,程式就會調用庫函數

    terminate

例子

#include <string>
#include <iostream>

using namespace std;

class MyException{};
class Dummy {
public:
    // 構造函數
    Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); }
    // 拷貝構造
    Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); }
    // 析構函數
    ~Dummy(){ PrintMsg("Destroyed Dummy:"); }
    void PrintMsg(string s) { cout << s  << MyName <<  endl; }
    string MyName;
    int level;
};

void C(Dummy d, int i) {
    cout << "Entering Function C" << endl;
    d.MyName = " C";
    throw MyException();

    cout << "Exiting Function C" << endl;
}

void B(Dummy d, int i) {
    cout << "Entering Function B" << endl;
    d.MyName = " B";
    C(d, i + 1);
    cout << "Exiting Function B" << endl;
}

void A(Dummy d, int i) {
    cout << "Entering Function A" << endl;
    d.MyName = " A" ;
  //  Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!!
    B(d, i + 1);
 //   delete pd;
    cout << "Exiting FunctionA" << endl;
}

int main() {
    cout << "Entering main" << endl;
    try {
        Dummy d(" M");
        A(d,1);
    }
    catch (MyException& e) {
        cout << "Caught an exception of type: " << typeid(e).name() << endl;
    }
    cout << "Exiting main." << endl;
    return 0;
}

/*


*/
           

進行編譯,運作,可得到如下結果:

$ g++ stack_unwinding.cpp -o stack_test -std=c++11

$ ./stack_test                                    
Entering main
Created Dummy: M
Copy created Dummy: M
Entering Function A
Copy created Dummy: A
Entering Function B
Copy created Dummy: B
Entering Function C
Destroyed Dummy: C
Destroyed Dummy: B
Destroyed Dummy: A
Destroyed Dummy: M
Caught an exception of type: 11MyException
Exiting main.
           

程式運作時對應棧的内容如下圖所示:

棧展開(stack unwinding)
  1. 根據建立

    Dummy

    對象的順序,在它們超出範圍時将其銷毀。
  2. 除了包含 catch 語句的

    main

    之外,其他函數均未完成。
  3. 函數

    A

    絕不會從其對

    B()

    的調用傳回,并且

    B

    C()

    的調用傳回。

reference

繼續閱讀