1. 程式人生 > 其它 >棧展開(stack unwinding)

棧展開(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.

程式執行時對應棧的內容如下圖所示:

程式執行將從 C 中的 throw 語句跳轉到 main 中的 catch 語句,並在此過程中展開每個函式。

  1. 根據建立 Dummy 物件的順序,在它們超出範圍時將其銷燬
  2. 除了包含 catch 語句的 main 之外,其他函式均未完成。
  3. 函式 A 絕不會從其對 B() 的呼叫返回,並且 B 絕不會從其對 C() 的呼叫返回。

reference

[1] microsoft C++文件

[2] 丟擲異常與棧展開(stack unwinding)