C 多繼承的細節

2021-04-12 16:09:56 字數 3968 閱讀 1993

這幾天寫的程式應用到多繼承。

以前對多繼承的概念非常清晰,可是很久沒用就有點模糊了。重新研究一下,「重新整理」下記憶。

假設我們有下面的**:

#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做快取,待查。

前一段時間因為擔心.net中將inte***ce轉成相應的類會有問題。今天對c++多重繼承的複習徹底消除了疑雲。 

C 多繼承的細節

這幾天寫的程式應用到多繼承。以前對多繼承的概念非常清晰,可是很久沒用就有點模糊了。重新研究一下,重新整理 下記憶。假設我們有下面的 include class a virtual void show virtual void dispa class b virtual void show virtu...

C 多繼承的細節

這幾天寫的程式應用到多繼承。以前對多繼承的概念非常清晰,可是很久沒用就有點模糊了。重新研究一下,重新整理 下記憶。假設我們有下面的 include class a virtual void show virtual void dispa class b virtual void show virtu...

C 多繼承的細節

這幾天寫的程式應用到多繼承。以前對多繼承的概念非常清晰,可是很久沒用就有點模糊了。重新研究一下,重新整理 下記憶。假設我們有下面的 include class a virtual void show virtual void dispa class b virtual void show virtu...