Assembly 繼承和多態(tài)

2018-12-04 17:43 更新

繼承 (Inheritance)允許一個(gè)類繼承另一個(gè)類的數(shù)據(jù)和成員函數(shù)。例如, 考慮圖 7.19中的代碼。它展示了兩個(gè)類,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()


簡(jiǎn)單繼承


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


簡(jiǎn)單繼承的匯編代碼


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


多態(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)在,對(duì)f的第二次調(diào)用調(diào)用了B::m()的成員函數(shù),因?yàn)樗鼈鬟f了對(duì)象B。但是,這并不是唯一的修改的地方。A的大小現(xiàn)在為8(而B為12)。同樣,ad的偏移為4,不是0。在偏移0處是的什么呢?這個(gè)問題的答案與如何實(shí)現(xiàn)多態(tài)相關(guān)。


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


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


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


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


示例

示例2


b1的內(nèi)部表示


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


從這里我們可以學(xué)到一些實(shí)踐的教訓(xùn)。一個(gè)重要的事實(shí)是當(dāng)你讀或?qū)戭?變量到一個(gè)二進(jìn)制源文件中時(shí),你必須非常小心。你不可以在整個(gè)對(duì)象中僅僅使用一個(gè)二進(jìn)制讀或?qū)懀驗(yàn)榭赡軙?huì)讀或?qū)懺次募獾膙table指針! 這是一個(gè)指向留在程序內(nèi)存中的vtable的指針,而且不同的程序?qū)⒉煌?。同樣的問題會(huì)發(fā)生在C語言的結(jié)構(gòu)中,但是在C語言中,結(jié)構(gòu)體只有當(dāng)程序員明確將指針放到結(jié)構(gòu)體中時(shí),結(jié)構(gòu)體內(nèi)部才有指針。類A或類B中,并沒有明顯地定義過指針。 


程序輸出


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


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


 C++的其它特性 

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

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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)