一、真空類
class CNull { }; |
長度:1
內存結構:
?? |
評註:長度其實爲0,這個字節作爲內容沒有意義,可能每次都不一樣。
二、空類
class CNull2 { public: CNull2(){printf("Construct/n");} ~CNull2(){printf("Desctruct/n");} void Foo(){printf("Foo/n");} }; |
長度:1
內存結構:
?? |
評註:同真空類差不多,內部的成員函數並不會影響類大小。
三、簡單類
class COneMember { public: COneMember(int iValue = 0){m_iOne = iValue;}; private: int m_iOne; }; |
長度:4
內存結構:
00 00 00 00 //m_iOne |
評註:成員數據才影響類大小。
四、簡單繼承
class CTwoMember:public COneMember { private: int m_iTwo; }; |
長度:8
內存結構:
00 00 00 00 //m_iOne CC CC CC CC //m_iTwo |
評註:子類成員接在父類成員之後。
五、再繼承
class CThreemember:public CTwoMember { public: CThreemember(int iValue=10) {m_iThree = iValue;}; private: int m_iThree; }; |
長度:12
內存結構:
00 00 00 00 //m_iOne CC CC CC CC //m_iTwo 0A 00 00 00 //m_iThree |
評註:孫類成員接在子類之後,再再繼承就依此類推了。
六、多重繼承
class ClassA { public: ClassA(int iValue=1){m_iA = iValue;}; private: int m_iA; };
class ClassB { public: ClassB(int iValue=2){m_iB = iValue;}; private: int m_iB; };
class ClassC { public: ClassC(int iValue=3){m_iC = iValue;}; private: int m_iC; };
class CComplex :public ClassA, public ClassB, public ClassC { public: CComplex(int iValue=4){m_iComplex = iValue;}; private: int m_iComplex; };
|
長度:16
內存結構:
01 00 00 00 //A 02 00 00 00 //B 03 00 00 00 //C 04 00 00 00 //Complex |
評註:也是父類成員先出現在前邊,我想這都足夠好理解。
七、複雜一些的繼承
不寫代碼了,怕讀者看了眼花,改畫圖。
長度:32
內存結構:
01 00 00 00 //A 02 00 00 00 //B 03 00 00 00 //C 04 00 00 00 //Complex 00 00 00 00 //OneMember CC CC CC CC //TwoMember 0A 00 00 00 //ThreeMember 05 00 00 00 //VeryComplex |
評註:還是把自己的成員放在最後。
只要沒涉及到“虛”(Virtual),我想沒什麼難點,不巧的是“虛”正是我們要研究的內容。
八、趁熱打鐵,看“虛繼承”
class CTwoMember:virtual public COneMember { private: int m_iTwo; }; |
長度:12
內存結構:
E8 2F 42 00 //指針,指向一個關於偏移量的數組,且稱之虛基類偏移量表指針 CC CC CC CC // m_iTwo 00 00 00 00 // m_iOne(虛基類數據成員) |
評註:virtual讓長度增加了4,其實是多了一個指針,關於這個指針,確實有些複雜,別的文章有具體分析,這裏就不岔開具體講了,可認爲它指向一個關於虛基類偏移量的數組,偏移量是關於虛基類數據成員的偏移量。
九、“閉合”虛繼承,看看效果
長度:24
內存結構:
14 30 42 00 //ClassB的虛基類偏移量表指針 02 00 00 00 //m_iB C4 2F 42 00 //ClassC的虛基類偏移量表指針 03 00 00 00 //m_iC 04 00 00 00 //m_iComplex 01 00 00 00 //m_iA |
評註:和預料中的一樣,虛基類的成員m_iA只出現了一次,而且是在最後邊。當然了,更復雜的情況要比這個難分析得多,但虛繼承不是我們研究的重點,我們只需要知道:虛繼承利用一個“虛基類偏移量表指針”來使得虛基類即使被重複繼承也只會出現一次。
十、看一下關於static成員
class CStaticNull { public: CStaticNull(){printf("Construct/n");} ~CStaticNull(){printf("Desctruct/n");} static void Foo(){printf("Foo/n");} static int m_iValue; }; |
長度:1
內存結構:(同CNull2)
評註:可見static成員不會佔用類的大小,static成員的存在區域爲靜態區,可認爲它們是“全局”的,只是不提供全局的訪問而已,這跟C的static其實沒什麼區別。
十一、帶一個虛函數的空類
class CVirtualNull { public: CVirtualNull(){printf("Construct/n");} ~CVirtualNull(){printf("Desctruct/n");} virtual void Foo(){printf("Foo/n");} }; |
長度:4
內存結構:
00 31 42 00 //指向虛函數表的指針(虛函數表後面簡稱“虛表”)
00423100:(虛表) 41 10 40 00 //指向虛函數Foo的指針
00401041: E9 78 02 00 00 E9 C3 03 … //函數Foo的內容(看不懂) |
評註:帶虛函數的類長度就增加了4,這個4其實就是個指針,指向虛函數表的指針,上面這個例子中虛表只有一個函數指針,值就是“0x00401041”,指向的這個地址就是函數的入口了。
十二、繼承帶虛函數的類
class CVirtualDerived : public CVirtualNull { public: CVirtualDerived(){m_iVD=0xFF;}; ~CVirtualDerived(){}; private: int m_iVD; }; |
長度:8
內存結構:
3C 50 42 00 //虛表指針 FF 00 00 00 //m_iVD
0042503C:(虛表) 23 10 40 00 //指向虛函數Foo的指針,如果這時候創建一個CVirtualNull對象,會發現它的虛表的內容跟這個一樣 |
評註:由於父類帶了虛函數,子類就算沒有顯式聲明虛函數,虛表還是存在的,虛表存放的位置跟父類不同,但內容是同的,也就是對父類虛表的複製。
十三、子類有新的虛函數
class CVirtualDerived: public CVirtualNull { public: CVirtualDerived(){m_iVD=0xFF;}; ~CVirtualDerived(){}; virtual void Foo2(){printf("Foo2/n");}; private: int m_iVD; }; |
長度:8
內存結構:
24 61 42 00 //虛表指針 FF 00 00 00 //m_iVD
00426124:(虛表) 23 10 40 00 50 10 40 00 |
評註:虛表還是隻有一張,不會因爲增加了新的虛函數而多出另一張來,新的虛函數的指針將添加在複製了的虛表的後面。
十四、當純虛函數(pure function)出現時
class CPureVirtual { virtual void Foo() = 0; };
class CDerivePV : public CPureVirtual { void Foo(){printf("vd: Foo/n");}; }; |
長度:4(CPureVirtual),4(CDerivePV)
內存結構:
CPureVirtual: (不可實例化)
CDerivePV: 28 50 42 00 //虛表指針
00425028:(虛表) 5A 10 40 00 //指向Foo的函數指針 |
評註:帶純虛函數的類不可實例化,因此列不出其“內存結構”,由其派生類實現純虛函數。我們可以看到CDerivePV雖然沒有virtual聲明,但由於其父類帶virtual,所以還是繼承了虛表,如果CDerivePV有子類,還是這個道理。
十五、虛函數類的多重繼承
前面提到:(子類的虛表)不會因爲增加了新的虛函數而多出另一張來,但如果有多重繼承的話情況就不是這樣了。下例中你將看到兩張虛表。
大小:24
內存結構
F8 50 42 00 //虛表指針 01 00 00 00 //m_iA 02 00 00 00 //m_iB E8 50 42 00 //虛表指針 03 00 00 00 //m_iC 04 00 00 00 //m_iComplex
004250F8:(虛表) 5A 10 40 00 //FooA 55 10 40 00 //FooB 64 10 40 00 //FooComplex
004250E8:(虛表) 5F 10 40 00 //FooC |
評註:子類的虛函數接在第一個基類的虛函數表的後面,所以B接在A後面,Complex接在B後面。基類依次出現,子類成員接在最後面,所以m_iComplex位於最後面。
十六、包含虛函數類的虛繼承
[cpp] view plain copy
class VirtualInheritance
{
char k[3];
public:
virtual void aa(){};
};
class sonClass1: public virtual VirtualInheritance
{
char j[3];
public:
virtual void bb(){};
};
class sonClass2: public virtual sonClass1
{
char f[3];
public:
virtual void cc(){};
};
int main()
{
cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;
cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;
cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;
return 0;
}
輸出的結果:
visio studio: 8,20,32
gcc: 8,16,24
評註:
對於VirtualInheritance類,大小爲8, 沒有異議,他有個虛表指針vtp_VirtualInheritanc;
對於sonClass1類:
在visio studio 編譯器下,大小爲20。由於是虛擬繼承,又有自己的虛函數,所以先擁有一個自己的虛函數指針vpt_sonClass1,大小爲4,指向自己的虛表;還要有一個char[3],大小爲4;爲了實現虛擬繼承,首先sonClass1加入了一個指向其父類的虛類指針,記作vtp_sonClass1_VirtualInheritanc,大小爲4;然後在加上父類的所有大小8,所以總共是20字節。
在gcc編譯器下,大小爲16,沒有計算子類中指向父類的虛類指針vtp_sonClass1_VirtualInheritanc的大小。
對於sonClass2:
在visio studio環境下,大小爲32。和上面一樣,子類擁有char[3],大小爲4字節,因爲是虛繼承,還有自己的虛函數,所以擁有自己的一個虛表指針,vtp_sonClass2,大小爲4字節。然後還有一個指向父類的虛類指針vtp_sonClass2_sonClass
1,大小爲4。最後加上其父類的總大小20,所以總的大小爲4+4+4+20=32;
在gcc環境下,沒有計算虛類指針的大小,即4+4+16=24。
17、包含虛函數的多重普通、虛擬混合繼承
[cpp] view plain copy
class VirtualInheritance
{
char k[3];
public:
virtual void aa(){};
};
class sonClass1: public virtual VirtualInheritance
{
char j[3];
public:
virtual void bb(){};
};
class VirtualInheritance2
{
char l[3];
public:
virtual void dd(){};
};
class sonClass2: public virtual sonClass1,public VirtualInheritance2
{
char f[3];
public:
virtual void cc(){};
};
int main()
{
cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;
cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;
cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;
return 0;
}
評註:此時sonClass2的大小變成36。和16不同的是,此時sonClass2是多重繼承,其中一個是虛繼承,一個普通繼承,他的大小在visio studio中變成36,相比16增加了4,這剛好是char l[3]的大小,因爲聳sonClass2已經有了一個虛表,所以在他原有的虛表中多一條記錄即可。
18、包含虛函數的多重虛擬繼承
[cpp] view plain copy
class VirtualInheritance
{
char k[3];
public:
virtual void aa(){};
};
class sonClass1: public virtual VirtualInheritance
{
char j[3];
public:
virtual void bb(){};
};
class VirtualInheritance2
{
char l[3];
public:
virtual void dd(){};
};
class sonClass2: public virtual sonClass1,public virtual VirtualInheritance2
{
char f[3];
public:
virtual void cc(){};
};
int main()
{
cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;
cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;
cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;
return 0;
}
評註:此時sonClass2的大小變成40。與17不同的是,sonClass2的多重繼承都是虛擬繼承。sonClass2的大小由以下幾部分構成:
1.自己本身的大小,char[3] 大小爲4,一個虛函數,所以有個指向虛表的指針,大小爲4,所以自身大小總的爲8;
2.虛擬繼承sonClass1,因爲虛擬繼承所以有個虛類指針ptr_sonClass2_sonClass1,大小爲4,而sonClass1的大小爲20,所以虛擬繼承sonClass1的大小爲24;
3.虛擬繼承VirtualInheritance2,一個虛類指針ptr_sonClass2_VirtualInheritance2=ptr_sonClass2_sonClass1+偏移量,該指針和ptr_sonClass2_sonClass1公用一個指針,只是偏移量不同,所以大小爲0(即使再多繼承幾個virtual class,這個指針的大小隻算 一次),而VirtualInheritance2的大小爲8,所以總的大小爲8。
所以40=8+24+8
總結:
1,普通單繼承,只需將自身成員變量的大小加上父類大小(父類中 有虛函數,子類中不管有沒有)若父類沒有虛函數,則子類大小需要加上指向虛表的指針大小。
2,普通多繼承,若幾個父類都有虛表,則子類與第一個父類公用一個虛表指針,其他有幾個有虛函數的父類則就有幾個虛表指針。
3,虛擬單繼承,此時若子類有虛函數則加上一個自身的虛表指針的大小,(若沒有則不加)再加上自身的成員變量大小,還要加上一個虛類指針ptr_sonclass_fatherclass,最後加上父類的大小。
4,多重虛擬繼承,此時若子類有虛函數則加上一個自身的虛表指針的大小,(若沒有則不叫)再加上自身的成員變量大小,還要加上 一個公用的虛類指針(不管有幾個虛擬父類,只加一個),在加上所有父類的大小。
5、普通、虛擬混合多繼承,此時子類的大小爲自身大小(若子類或普通父類有虛函數,則爲成員變量+虛表指針大小;若都沒虛函數,則就爲成員變量大小),加上一個虛類指針大小,在加上虛擬父類的大小,在加上普通父類的大小(除虛表指針,因爲它和子類公用一個虛表指針)。
歡迎補充!