天天看點

怎樣計算C++下繼承、虛繼承、虛函數類的大小

一、真空類

C++代碼

  1. class CNull  
  2. {  
  3. };  

  長度:1

  記憶體結構:

  ??

  評注:長度其實為0,這個位元組作為内容沒有意義,可能每次都不一樣。

  二、空類

C++代碼

  1. class CNull2  
  2. {  
  3. public:  
  4.   CNull2(){printf("Construct/n");}  
  5.   ~CNull2(){printf("Desctruct/n");}  
  6.   void Foo(){printf("Foo/n");}  
  7. };  

  長度:1

  記憶體結構:

  ??

  評注:同真空類差不多,内部的成員函數并不會影響類大小。

  三、簡單類

C++代碼

  1. class COneMember  
  2. {  
  3. public:  
  4.     COneMember(int iValue = 0){m_iOne = iValue;};  
  5. private:  
  6.     int m_iOne;  
  7. };  

  長度:4

  記憶體結構:

  00 00 00 00 //m_iOne

  評注:成員資料才影響類大小。

  四、簡單繼承

C++代碼

  1. class CTwoMember:public COneMember  
  2. {  
  3. private:  
  4.     int m_iTwo;  
  5. };  

  長度:8

  記憶體結構:

  00 00 00 00 //m_iOne

  CC CC CC CC //m_iTwo

  評注:子類成員接在父類成員之後。

  五、再繼承

C++代碼

  1. class CThreemember:public CTwoMember  
  2. {  
  3. public:  
  4.     CThreemember(int iValue=10) {m_iThree = iValue;};  
  5. private:  
  6.     int m_iThree;  
  7. };  

  長度:12

  記憶體結構:

  00 00 00 00 //m_iOne

  CC CC CC CC //m_iTwo

  0A 00 00 00 //m_iThree

  評注:孫類成員接在子類之後,再再繼承就依此類推了。

  六、多重繼承

C++代碼

  1. class ClassA  
  2. {  
  3. public:  
  4.     ClassA(int iValue=1){m_iA = iValue;};  
  5. private:  
  6.     int m_iA;  
  7. };  
  8. class ClassB  
  9. {  
  10. public:  
  11.     ClassB(int iValue=2){m_iB = iValue;};  
  12. private:  
  13.     int m_iB;  
  14. };  
  15. class ClassC  
  16. {  
  17. public:  
  18.     ClassC(int iValue=3){m_iC = iValue;};  
  19. private:  
  20.     int m_iC;  
  21. };  
  22. class CComplex :public ClassA, public ClassB, public ClassC  
  23. {  
  24. public:  
  25.     CComplex(int iValue=4){m_iComplex = iValue;};  
  26. private:  
  27.     int m_iComplex;  
  28. };  

  長度:16

  記憶體結構:

  01 00 00 00  //A

  02 00 00 00  //B

  03 00 00 00  //C

  04 00 00 00  //Complex

  評注:也是父類成員先出現在前邊,我想這都足夠好了解。

  七、複雜一些的繼承

  不寫代碼了,怕讀者看了眼花,改畫圖。

怎樣計算C++下繼承、虛繼承、虛函數類的大小

  長度: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),我想沒什麼難點,不巧的是“虛”正是我們要研究的内容。

  八、趁熱打鐵,看“虛繼承”

C++代碼

  1. class CTwoMember:virtual public COneMember  
  2. {  
  3. private:  
  4.     int m_iTwo;  
  5. };  

  長度:12

  記憶體結構:

  E8 2F 42 00 //指針,指向一個關于偏移量的數組,且稱之虛基類偏移量表指針

  CC CC CC CC // m_iTwo

  00 00 00 00 // m_iOne(虛基類資料成員)

  評注:virtual讓長度增加了4,其實是多了一個指針,關于這個指針,确實有些複雜,别的文章有具體分析,這裡就不岔開具體講了,可認為它指向一個關于虛基類偏移量的數組,偏移量是關于虛基類資料成員的偏移量。

  九、“閉合”虛繼承,看看效果

怎樣計算C++下繼承、虛繼承、虛函數類的大小

  長度: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成員

C++代碼

  1. class CStaticNull  
  2. {  
  3. public:  
  4.     CStaticNull(){printf("Construct/n");}  
  5.     ~CStaticNull(){printf("Desctruct/n");}  
  6.     static void Foo(){printf("Foo/n");}  
  7.     static int m_iValue;  
  8. };  

  長度:1

  記憶體結構:(同CNull2)

  評注:可見static成員不會占用類的大小,static成員的存在區域為靜态區,可認為它們是“全局”的,隻是不提供全局的通路而已,這跟C的static其實沒什麼差別。

  十一、帶一個虛函數的空類

C++代碼

  1. class CVirtualNull  
  2. {  
  3. public:  
  4.     CVirtualNull(){printf("Construct/n");}  
  5.     ~CVirtualNull(){printf("Desctruct/n");}  
  6.     virtual void Foo(){printf("Foo/n");}  
  7. };  

  長度:4

  記憶體結構:

  00 31 42 00 //指向虛函數表的指針(虛函數表後面簡稱“虛表”)

  00423100:(虛表)

  41 10 40 00 //指向虛函數Foo的指針

  00401041:

  E9 78 02 00 00 E9 C3 03 … //函數Foo的内容(看不懂)

  評注:帶虛函數的類長度就增加了4,這個4其實就是個指針,指向虛函數表的指針,上面這個例子中虛表隻有一個函數指針,值就是“0x00401041”,指向的這個位址就是函數的入口了。

  十二、繼承帶虛函數的類

C++代碼

  1. class CVirtualDerived : public CVirtualNull  
  2. {  
  3. public:  
  4.     CVirtualDerived(){m_iVD=0xFF;};  
  5.     ~CVirtualDerived(){};  
  6. private:  
  7.     int m_iVD;  
  8. };  

  長度:8

  記憶體結構:

  3C 50 42 00 //虛表指針

  FF 00 00 00 //m_iVD

  0042503C:(虛表)

  23 10 40 00 //指向虛函數Foo的指針,如果這時候建立一個CVirtualNull對象,會發現它的虛表的内容跟這個一樣

  評注:由于父類帶了虛函數,子類就算沒有顯式聲明虛函數,虛表還是存在的,虛表存放的位置跟父類不同,但内容是同的,也就是對父類虛表的複制。

  十三、子類有新的虛函數

C++代碼

  1. class CVirtualDerived: public CVirtualNull  
  2. {  
  3. public:  
  4.     CVirtualDerived(){m_iVD=0xFF;};  
  5.     ~CVirtualDerived(){};  
  6.     virtual void Foo2(){printf("Foo2/n");};  
  7. private:  
  8.     int m_iVD;  
  9. };  

  長度:8

  記憶體結構:

  24 61 42 00 //虛表指針

  FF 00 00 00 //m_iVD

  00426124:(虛表)

  23 10 40 00

  50 10 40 00

  評注:虛表還是隻有一張,不會因為增加了新的虛函數而多出另一張來,新的虛函數的指針将添加在複制了的虛表的後面。

  十四、當純虛函數(pure function)出現時

C++代碼

  1. class CPureVirtual  
  2. {  
  3.     virtual void Foo() = 0;  
  4. };   
  5. class CDerivePV : public CPureVirtual  
  6. {  
  7.     void Foo(){printf("vd: Foo/n");};  
  8. };  

  長度:4(CPureVirtual),4(CDerivePV)

  記憶體結構:

  CPureVirtual:

  (不可執行個體化)

  CDerivePV:

  28 50 42 00 //虛表指針

  00425028:(虛表)

  5A 10 40 00 //指向Foo的函數指針

  評注:帶純虛函數的類不可執行個體化,是以列不出其“記憶體結構”,由其派生類實作純虛函數。我們可以看到CDerivePV雖然沒有virtual聲明,但由于其父類帶virtual,是以還是繼承了虛表,如果CDerivePV有子類,還是這個道理。

  十五、虛函數類的多重繼承

  前面提到:(子類的虛表)不會因為增加了新的虛函數而多出另一張來,但如果有多重繼承的話情況就不是這樣了。下例中你将看到兩張虛表。

怎樣計算C++下繼承、虛繼承、虛函數類的大小

  大小: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位于最後面。

  十六、包含虛函數類的虛繼承

C++代碼

  1. class VirtualInheritance  
  2. {  
  3.     char k[3];  
  4. public:  
  5.     virtual void aa(){};  
  6. };  
  7. class sonClass1: public virtual VirtualInheritance  
  8. {  
  9.     char j[3];  
  10. public:  
  11.     virtual void bb(){};  
  12. };  
  13. class sonClass2: public virtual sonClass1  
  14. {  
  15.     char f[3];  
  16. public:  
  17.     virtual void cc(){};  
  18. };  
  19. int main()  
  20. {  
  21.     cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;  
  22.     cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;  
  23.     cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;  
  24.     return 0;  
  25. }  

  輸出的結果:

  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、包含虛函數的多重普通、虛拟混合繼承

C++代碼

  1. class VirtualInheritance  
  2. {  
  3.     char k[3];  
  4. public:  
  5.     virtual void aa(){};  
  6. };  
  7. class sonClass1: public virtual VirtualInheritance  
  8. {  
  9.     char j[3];  
  10. public:  
  11.     virtual void bb(){};  
  12. };  
  13. class VirtualInheritance2  
  14. {  
  15.     char l[3];  
  16. public:  
  17.     virtual void dd(){};  
  18. };  
  19. class sonClass2: public virtual sonClass1,public VirtualInheritance2  
  20. {  
  21.     char f[3];  
  22. public:  
  23.    virtual void cc(){};  
  24. };  
  25. int main()  
  26. {  
  27.     cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;  
  28.     cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;  
  29.     cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;  
  30.     return 0;  
  31. }  

  評注:此時sonClass2的大小變成36。和16不同的是,此時sonClass2是多重繼承,其中一個是虛繼承,一個普通繼承,他的大小在visio studio中變成36,相比16增加了4,這剛好是char l[3]的大小,因為聳sonClass2已經有了一個虛表,是以在他原有的虛表中多一條記錄即可。

  18、包含虛函數的多重虛拟繼承

C++代碼

  1. class VirtualInheritance  
  2. {  
  3.     char k[3];  
  4. public:  
  5.     virtual void aa(){};  
  6. };  
  7. class sonClass1: public virtual VirtualInheritance  
  8. {  
  9.     char j[3];  
  10. public:  
  11.     virtual void bb(){};  
  12. };  
  13. class VirtualInheritance2  
  14. {  
  15.     char l[3];  
  16. public:  
  17.     virtual void dd(){};  
  18. };  
  19. class sonClass2: public virtual sonClass1,public virtual VirtualInheritance2  
  20. {  
  21.     char f[3];  
  22. public:  
  23.    virtual void cc(){};  
  24. };  
  25. int main()  
  26. {  
  27.     cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;  
  28.     cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;  
  29.     cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;  
  30.     return 0;  
  31. }  

  評注:此時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、普通、虛拟混合多繼承,此時子類的大小為自身大小(若子類或普通父類有虛函數,則為成員變量+虛表指針大小;若都沒虛函數,則就為成員變量大小),加上一個虛類指針大小,在加上虛拟父類的大小,在加上普通父類的大小(除虛表指針,因為它和子類公用一個虛表指針)。

  歡迎補充!

除非特别注明,雞啄米文章均為原創

轉載請标明本文位址:http://www.jizhuomi.com/software/374.html

此文章源自于【http://www.jizhuomi.com/software/374.html】

繼續閱讀