Assembly 繼承和多態(tài)

2018-12-04 17:43 更新

繼承 (Inheritance)允許一個類繼承另一個類的數(shù)據(jù)和成員函數(shù)。例如, 考慮圖 7.19中的代碼。它展示了兩個類,A和B,其中類B是通過繼承類A得到的。程序的輸出如下:


Size of a: 4 Offset of ad: 0 

Size of b: 8 Offset of ad: 0 Offset of bd: 4 A::m() 

A::m()


簡單繼承


注意,兩個類的數(shù)據(jù)成員ad(B通過繼承A得到的)在相同的偏移處。這是非常重要的,因為f函數(shù)將傳遞一個指針到一個A對象或任意一個由A派生( 也就是 ,通過繼承得到)的對象類型中。圖 7.20展示了此函數(shù)的(編輯過的)匯編代碼(gcc得到的)。 


簡單繼承的匯編代碼


注意在輸出中,a和b對象調(diào)用的都是A的成員函數(shù)m。從匯編程序中,我們可以看到對A::m()的調(diào)用被硬編碼到函數(shù)中了。對于真正的面向對象編程,成員函數(shù)的調(diào)用取決于傳遞給函數(shù)的對象類型是什么。這就是所謂的 多態(tài) 。缺省情況下,C++關掉了這個特性。你可以使用virtual 關鍵字來激活它。圖 7.21展示了如何修改這兩個類。其它代碼不需要修改。多態(tài)可以用許多方法來實現(xiàn)。不幸的是,當在以這種方法書寫的時候,gcc的實現(xiàn)方法正處在改變中,而且與它最初的實現(xiàn)方法相比,明顯變得更復雜了。為了簡單化討論的目的,作者只涉及基于Microsoft和Borland編譯 器Windows使用的多態(tài)的實現(xiàn)方法。這種實現(xiàn)方法很多年沒有改變了,而且可能在未來幾年也不會改變。 


多態(tài)繼承


有了這些改變,程序的輸出如下:


Size of a: 8 Offset of ad: 4 

Size of b: 12 Offset of ad: 4 Offset of bd: 8 A::m() 

B::m()


現(xiàn)在,對f的第二次調(diào)用調(diào)用了B::m()的成員函數(shù),因為它傳遞了對象B。但是,這并不是唯一的修改的地方。A的大小現(xiàn)在為8(而B為12)。同樣,ad的偏移為4,不是0。在偏移0處是的什么呢?這個問題的答案與如何實現(xiàn)多態(tài)相關。


f()函數(shù)的匯編代碼


含有任意虛成員函數(shù)的C++類有一個額外的隱藏的域,它是一張指向成員函數(shù)指針數(shù)組的指針表。這個表通常稱為vtable。對于A和B類,指針表儲存在偏移地址0處。Windows編譯器總是把此指針表放到繼承樹頂部 的類的開始處。從擁有虛成員函數(shù)的程序版本(源自圖 7.19)中的f函數(shù)產(chǎn) 生的匯編代碼(圖 7.22)中,你可以看到對成員函數(shù)m的調(diào)用不是使用一個標號。第9行來查找對象的vtable的地址。對象的地址在第11行中被壓入堆 棧。第12行通過分支到vtable里的第一個地址處來調(diào)用虛成員函數(shù)。這 次調(diào)用并不使用一個標號,它分支到EDX指向的代碼地址處。這種類型的調(diào)用是一個晚綁定 (late binding)的例子。晚綁定將調(diào)用哪個成員函數(shù)的判定 延遲到代碼運行時。這就允許代碼為對象調(diào)用恰當?shù)某蓡T函數(shù)。標準的案 例(圖 7.20)硬編碼某個成員函數(shù)的調(diào)用,也稱為 早綁定 (early binding) (因 為這兒成員函數(shù)被早綁定了,在編譯的時候。)。 


用心的讀者將會覺得奇怪為什么在圖 7.21中的類的成員函數(shù)通過使 用_ _cdecl關鍵字來明確聲明使用的是C調(diào)用約定。缺省情況下,Microsoft對 于C++類成員函數(shù)使用的是不同的調(diào)用約定,而不是標準C調(diào)用約定。此調(diào)用約定將指向成員函數(shù)能起作用的對象的指針傳遞到ECX寄存器,而不 是使用堆棧。成員函數(shù)的其它明確的參數(shù)仍然使用堆棧。修改為_ _cdecl告訴編譯器使用標準C調(diào)用約定。Borland C++缺省情況下使用的是C調(diào)用約定。 


下面我們再看一個稍微復雜一點的例子。(圖 7.23)。在這個例子中, 類A和B都有兩個成員函數(shù):m1和m2。記住因為類B并沒有定義自己的成員函數(shù)m2,它繼承了A類的成員函數(shù)。圖 7.24展示了對象b在內(nèi)存中如何儲存。圖 7.25展示了此程序的輸出。首先,看看每個對象的vtable的地址。兩個B對象的vtable地址是一樣的,因此他們共享同樣的vtable。一 張vtable表是類的屬性而不是一個對象(就如一個static數(shù)據(jù)成員)。其次, 看看在vtable里的地址。從匯編程序的輸出中,你可以確定成員函數(shù)m1指針在偏移地址 0處(或雙字 0)而m2在偏移地址 4處(雙字 1)。m2成員函數(shù)指針在 類A和B的vtable中是一樣的,因為類B從類A繼承了成員函數(shù)m2。 


示例

示例2


b1的內(nèi)部表示


第25行到32行展示了你可以通過從對象的vtable讀地址的方法來調(diào)用一個虛函數(shù)。成員函數(shù)地址通過一個清楚的this指針儲存到了一個C類型函數(shù)指針中了。從圖 7.25的輸出中,你可以看到它確實可以運行。但是,請 不要像這樣寫代碼!這只是用來舉例說明虛成員函數(shù)如何使用vtable。 


從這里我們可以學到一些實踐的教訓。一個重要的事實是當你讀或寫類 變量到一個二進制源文件中時,你必須非常小心。你不可以在整個對象中僅僅使用一個二進制讀或寫,因為可能會讀或寫源文件之外的vtable指針! 這是一個指向留在程序內(nèi)存中的vtable的指針,而且不同的程序將不同。同樣的問題會發(fā)生在C語言的結構中,但是在C語言中,結構體只有當程序員明確將指針放到結構體中時,結構體內(nèi)部才有指針。類A或類B中,并沒有明顯地定義過指針。 


程序輸出


再次,認識到不同的編譯器實現(xiàn)虛成員函數(shù)的方法是不一樣的是非 常重要的。在In Windows中,COM(組件對象模型,Component Object Model) 類對象使用vtable來實現(xiàn)COM接口。只有像Microsoft一樣用來 實現(xiàn)虛成員函數(shù)的編譯器才可以創(chuàng)建COM類。這也是為什么Borland采用和Microsoft一樣的實現(xiàn)方法的原因,也是為什么不可以用gcc來創(chuàng)建COM類的原因之一。


虛成員函數(shù)的代碼和非常虛的成員函數(shù)的代碼非常相像。只是調(diào)用它們 的代碼是不同的。如果匯編器能絕對保證調(diào)用哪個虛成員函數(shù),那么它可以忽略vtable,直接調(diào)用成員函數(shù)。( 例如 ,使用早綁定)。


 C++的其它特性 

C++其它特性的工作方式( 例如 ,除了處理繼承和多繼承,還有運行時類型識別)不屬于教程的范圍。如果讀者希望走得更遠一些,一個好的起點是Ellis和Stroustrup寫的The Annotated C++ Reference Manual和Stroustrup寫的The Design and Evolution of C++。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號