要想學習好VC必須具備良好的C/C++的基礎,必要的英語閱讀能力也是必不可少的,因為大量的技術文檔多以英文形式發布,否則就會導致VC++編譯異常,這大大的影響了程序員的效率。
回憶一下我在第一節中介紹的EXCEPTION_REGISTRATION結構,我們曾用它向操作系統注冊了發生異常時要被調用的回調函數。VC++也是這麼做的,不過它擴展了這個結構的語義,在它的後面添加了兩個新字段:
- struct EXCEPTION_REGISTRATION
- {
- EXCEPTION_REGISTRATION* prev;
- DWORD handler;
- int id;
- DWORD ebp;
- };
VC++編譯異常會為絕大部分函數③添加一個EXCEPTION_REGISTRATION類型的局部變量,它的最後一個字段ebp)與棧桢指針指向的位置重疊。函 數的序言創建這個結構並把它注冊給操作系統,尾聲則恢復主調函數的EXCEPTION_REGISTRATION。id字段的意義我將在下一節介紹。
VC++編譯函數時會為它生成兩部分數據:
a)異常回調函數
b)一個包含函數重要信息的數據結構,這些信息包括catch塊、這些塊的地址和這些塊所關心的異常的類型等等。我把這個結構稱為funcinfo,有關它的詳細討論也在下一節。
是考慮了異常處理之後的運行時堆棧。widget的異常回調函數位於由FS:[0]指向的異常處理鏈的開始位置這是由widget的序言設置的)。
異常處理程序把widget的funcinfo結構的地址交給函數__CxxFrameHandler,__CxxFrameHandler會檢查這個結 構看函數中有沒有catch塊對當前的異常感興趣。
如果沒有的話,它就返回ExceptionContinueSearch給操作系統,於是操作系統會從 異常處理鏈表中取得下一個結點,並調用它的異常處理程序也就是調用當前函數的那個函數的異常處理程序)。
這一過程將一直進行下去——直到處理程序找到一個能處理當前異常的catch塊為止,這時它就不再返回操作系統了。但是在調用catch塊之前由於有 funcinfo結構,所以知道catch塊的入口,參見圖3),必須進行堆棧展開,也就是清理掉當前函數的棧桢下面的所有其他的棧桢。這個操作稍微有點 復雜。
因為:異常處理程序必須找到異常發生時生存在這些棧桢上的所有局部對象,VC++編譯異常並依次調用它們的析構函數。後面我將對此進行詳細介紹。 異常處理程序把這項工作委托給了各個棧桢自己的異常處理程序。從FS:[0]指向的異常處理鏈的第一個結點開始,它依次調用每個結點的處理程序,告訴它堆 棧正在展開。
與之相呼應,這些處理程序會調用每個局部對象的析構函數,然後返回。此過程一直進行到與異常處理程序自身相對應的那個結點為止。 由於catch塊是函數的一部分,所以它使用的也是函數的棧桢。因此,在調用catch塊之前,異常處理程序必須激活它所隸屬的函數的棧桢。
其次,每個catch塊都只接受一個參數,VC++編譯異常其類型是它希望捕獲的異常的類型。異常處理程序必須把異常對象本身或者是異常對象的引用拷貝到catch塊的棧 桢上,編譯器在funcinfo中記錄了相關信息,處理程序根據這些信息就能知道到哪去拷貝異常對象了。