在前一篇,咱們討論了在多繼承情況下,具備虛函數的類的內存佈局狀況。本篇將進一步探索在多重繼承,即具備重複繼承的狀況下的內存佈局狀況。在閱讀本篇和下一篇以前,建議先閱讀本博客另外一篇博文《淺析GCC下C++多重繼承 & 虛擬繼承的對象內存佈局》。ios
先說一點題外話,細心的讀者可能會發現,咱們在探索不一樣狀況下類的內存佈局時,老是先經過查看類的大小以及其中各個成員變量的地址來進行分析,而後再具體定位某一位置的值。從最原始的內存中的對象分佈,咱們能夠得到最深刻最有效的理解。函數
OK,請看例子:佈局
#include <iostream> using namespace std; class Top { public: virtual void x(){cout << "top x" << endl;} virtual void print0(){cout << "top print" << endl;} public: int a; }; class Left:public Top { public: virtual void y(){cout << "left y" << endl;} virtual void print1(){cout << "left print" << endl;} public: int b; }; class Right:public Top { public: virtual void z(){cout << "right z" << endl;} virtual void print2(){cout << "right print" << endl;} public: int c; }; class Bottom : public Left, public Right { public: virtual void y(){cout << "bottom y" << endl;} virtual void z(){cout << "bottom z" << endl;} virtual void print3(){cout << "bottom print" << endl;} public: int d; }; int main() { /*first part*/ cout << sizeof(Top) << "\t" << sizeof(Left) << "\t" << sizeof(Right) << "\t" << sizeof(Bottom) << endl; //輸出:8 12 12 28 Bottom *b = new Bottom(); cout << b << " " << &b->Left::a << " " << &b->b << " " << &b->Right::a << " " << &b->c << " " << &b->d << endl; //輸出:0x8c0f008 0x8c0f00c 0x8c0f010 0x8c0f018 0x8c0f01c 0x8c0f020 /*second part*/ typedef void (*Func)(void); Func pFunc; pFunc = (Func)*((int *)*(int *)(b)); pFunc();//輸出:top x pFunc = (Func)*((int *)*(int *)(b)+1); pFunc();//輸出:top print pFunc = (Func)*((int *)*(int *)(b)+2); pFunc();//輸出:bottom y pFunc = (Func)*((int *)*(int *)(b)+3); pFunc();//輸出:left print pFunc = (Func)*((int *)*(int *)(b)+4); pFunc();//輸出:bottom z pFunc = (Func)*((int *)*(int *)(b)+5); pFunc();//輸出:bottom print //pFunc = (Func)*((int *)*(int *)(b)+6); //pFunc();//段錯誤 /*third part*/ pFunc = (Func)*((int *)*((int *)(b)+3)); pFunc();//輸出:top x pFunc = (Func)*((int *)*((int *)(b)+3)+1); pFunc();//輸出:top print pFunc = (Func)*((int *)*((int *)(b)+3)+2); pFunc();//輸出:bottom z pFunc = (Func)*((int *)*((int *)(b)+3)+3); pFunc();//輸出:right print //pFunc = (Func)*((int *)*((int *)(b)+3)+4); //pFunc();//段錯誤 delete b; return 0; }
對於上面的例子,咱們分爲三部分進行講解。
第一部分:多重繼承狀況下,對象自己(除虛函數表外)的內存佈局。spa
從代碼中first part的輸出狀況來看,Top、Left和Right的大小很容易理解,至於Bottom類的大小,若是你看過參考文獻一,那麼也很容易理解。Bottom類包含了兩次Top類中的a成員變量,所以總共有5個int成員變量,爲20字節。再加上兩個虛指針,即爲28字節。.net
從輸出的Bottom對象中成員變量地址狀況,咱們能夠用以下圖來表示內存佈局:指針
簡單地說,多重繼承時,子類會有多個祖父類的存在。code
第二部分:多重繼承狀況下,主要虛函數表的內存佈局。orm
從代碼的second part的輸出狀況來看,咱們能夠用下圖來表示主要虛函數表的內存佈局:對象
第三部分:多重繼承狀況下,次要虛函數表的內存佈局。blog
從代碼的third part的輸出狀況來看,咱們能夠用下圖來表示次要虛函數表的內存佈局:
對比前一篇多繼承狀況下類的內存佈局,咱們能夠發現:在多重繼承狀況下,不只祖父類的成員變量在子類中會有多份存在,祖父類的虛函數一樣會在子類的虛函數表中有多份存在,分別位於主要虛函數表和次要虛函數表中。
參考文獻:
1.《淺析GCC下C++多重繼承 & 虛擬繼承的對象內存佈局》
2. 《深度探索C++對象模型》