C 下的多繼承

2021-04-20 05:45:42 字數 3843 閱讀 9413

下面是**的一篇關於c++下多繼承的實現及其原理。

#include

class a

virtual void show();

virtual void dispa();

}; class b

virtual void show();

virtual void dispb();

}; class c

virtual void show();

virtual void dispc();

}; class d : public a, public b, public c

virtual void show();

virtual void dispd();

}; class e : public d

virtual void show();

virtual void dispe();

}; int main()

每個類都有兩個虛函式show()和dispx()。類a,b,c是基本類,而d是多繼承,最後e又繼承了d。那麼對於類e,它的記憶體映像是怎樣的呢?為了解答這個問題,我們回顧一下基本類的記憶體映像:

+ --------------+ <- this

+    vtab       +

+ --------------+

+               +

+    data       +

+               +

+ --------------+

如果乙個類有虛函式,那麼它就有虛函式表(vtab)。類的第乙個單元是乙個指標,指向這個虛函式表。如果類沒有虛函式,並且它的祖先(所有父類)均沒有虛函式,那麼它的記憶體映像和c的結構一樣。所謂虛函式表就是乙個陣列,每個單元指向乙個虛函式位址。

如果類y是類x的乙個繼承,那麼類y的記憶體映像如下:

+ --------------+ <- this

+   y 的 vtab   +

+ --------------+

+               +

+   x 的 data   +

+               +

+ --------------+

+               +

+   y 的 data   +

+               +

+ --------------+

y的虛函式表基本和x的相似。如果y有新的虛函式,那麼就在vtab的末尾加上乙個。如果y重新定義了原有的虛函式,那麼原的指標指向新的函式入口。這樣無論是記憶體印象和虛函式表,y都和x相容。這樣當執行 x* x = (y*)y;之後,x可以很好的被運用,並且可以享受新的虛函式。

現在看多重繼承:

class d : public a, public b, public c

它的記憶體映像如下:  

+ --+ -----------------+ 00h <- this

+   +    d 的 vtab     +

+ a + -----------------+ 04h

+   +    a 的 資料     +

+ --+ -----------------+ 08h

+   +    b 的 vtab'    +

+ b + -----------------+ 0ch

+   +    b 的 資料     +

+ --+ -----------------+ 10h

+   +    c 的 vtab'    +

+ c + -----------------+ 14h

+   +    c 的 資料     +

+ --+ -----------------+ 18h

+ d +    d 的 資料     +

+ --+ -----------------+

(因為對齊於雙字,a~d的資料雖然只是乙個char,但需要對齊到dword,所以佔4位元組)

對於a,它和單繼承沒有什麼兩樣。b和c被簡單地放在a的後面。如果它們虛函式在d中被重新定義過(比如show函式),那麼它們需要使用新的vtab,使被重定義的虛函式指到正確的位置上(這對於com或類似的技術是至關重要的)。最後,d的資料被放置到最後面。

對於e的記憶體映像問題就可以不說自明了。

下面我們看一下c++是如何處理

d *d;

......

b *b = (b*)d;

這樣的要求的。設定斷點,進入反彙編,你可以看到如下的彙編**:(因為ubb關係,將方括號替換成了大括號。看上去有點彆扭)

b *b = (b*)d;

00401062  cmp         dword ptr ,0

00401066  je          main+73h (401073h)

00401068  mov         eax,dword ptr

0040106badd         eax,8

0040106e  mov         dword ptr ,eax

00401071  jmp         main+7ah (40107ah)

00401073  mov         dword ptr ,0

0040107a  mov         ecx,dword ptr

0040107d  mov         dword ptr ,ecx

從上述彙編**可以看出:如果源(這裡是d)是null,那麼目標(這裡是b)也將被置為null,否則目標將指向源的位址並向下偏移8個位元組,正好就是上圖所示b的vtab位置。至於為什麼要用ebp-38h作快取,這是編譯器的演算法問題了。等以後有時間再研究。

接下來看乙個比較古怪的問題,這個也是我寫這篇文章的初衷:

根據上面的多繼承定義,如果給出乙個類b的例項b,我們是否可以求出d的例項?

為什麼要問這個問題。因為存在下面的可能性:

class b

; class d : public a, public b, public c

; ...

}; class z : public x, public y, public b

; ...

}; void myfunc(b* b) }

猛一看很值得懷疑。但仔細想想,這是可能的,事實也證明了這一點。因為編譯器了解這d和b這兩個類相互之間的關係(也就是偏移量),因此它會做相應的轉換。同樣,設定斷點,檢視彙編:

d *d2 = (d*)b;

00419992  cmp         dword ptr ,0

00419996  je          main+196h (4199a6h)

00419998  mov         eax,dword ptr

0041999bsub         eax,8

0041999e  mov         dword ptr ,eax

004199a4  jmp         main+1a0h (4199b0h)

004199a6  mov         dword ptr ,0

004199b0  mov         ecx,dword ptr

004199b6  mov         dword ptr ,ecx

如果源(這裡是b)為null,那麼目標(這裡是d2)也為null。否則目標取源的位址並向上偏移8個位元組,這樣正好指向d的例項位置。同樣,為啥需要ebp-13ch做快取,待查。

C 繼承記憶體布局 虛繼承下的多繼承情況

前面介紹了單繼承以及正常多繼承的記憶體分布情況,最後介紹一種虛繼承的情況。同時虛繼承也是解決多繼承二義性的一種手段。首先將 改動如下,base1跟base11是完全一樣的 除了繼承的時候base1是虛繼承,我們先看下單繼承的時候,虛繼承的記憶體分布跟非虛繼承有什麼區別。可以看到虛繼承的記憶體分布要複...

C 多繼承 虛繼承

一,多繼承 include include using namespace std class b1 繼承類c void main 主函式 c類按照順序繼承b2,b1,b4,b3 再按照資料成員定義順序 memberb1,memberb4,memberb3,memberb2 最後是自己的構造器 二,...

C 多繼承 多重繼承

派生類都只有乙個基類,稱為單繼承。除此之外,c 也支援多繼承,即乙個派生類可以有兩個或多個基類。多繼承的語法也很簡單,將多個基類用逗號隔開。例如已宣告了類a 類b和類c,那麼可以這樣來宣告派生類d class d public a,private b,protected c d 是多繼承形式的派生類...