這幾天寫的程式應用到多繼承。
以前對多繼承的概念非常清晰,可是很久沒用就有點模糊了。重新研究一下,「重新整理」下記憶。
假設我們有下面的**:
#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...