一、真空类

C++代码
  1. class CNull  
  2. {  
  3.   
  4. };  

  长度: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.   
  9. class ClassB  
  10. {  
  11. public:  
  12.     ClassB(int iValue=2){m_iB = iValue;};  
  13. private:  
  14.     int m_iB;  
  15. };  
  16.   
  17. class ClassC  
  18. {  
  19. public:  
  20.     ClassC(int iValue=3){m_iC = iValue;};  
  21. private:  
  22.     int m_iC;  
  23. };  
  24.   
  25. class CComplex :public ClassA, public ClassB, public ClassC  
  26. {  
  27. public:  
  28.     CComplex(int iValue=4){m_iComplex = iValue;};  
  29. private:  
  30.     int m_iComplex;  
  31. };  

  长度: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.   
  6. class CDerivePV : public CPureVirtual  
  7. {  
  8.     void Foo(){printf("vd: Foo/n");};  
  9. };  

  长度: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.   
  20. int main()  
  21. {  
  22.     cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;  
  23.     cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;  
  24.     cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;  
  25.   
  26.     return 0;  
  27. }  

  输出的结果:

  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.   
  14. class VirtualInheritance2  
  15. {  
  16.     char l[3];  
  17. public:  
  18.     virtual void dd(){};  
  19. };  
  20.   
  21. class sonClass2: public virtual sonClass1,public VirtualInheritance2  
  22. {  
  23.     char f[3];  
  24. public:  
  25.    virtual void cc(){};  
  26. };  
  27.   
  28. int main()  
  29. {  
  30.     cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;  
  31.     cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;  
  32.     cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;  
  33.   
  34.     return 0;  
  35. }  

  评注:此时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.   
  14. class VirtualInheritance2  
  15. {  
  16.     char l[3];  
  17. public:  
  18.     virtual void dd(){};  
  19. };  
  20.   
  21. class sonClass2: public virtual sonClass1,public virtual VirtualInheritance2  
  22. {  
  23.     char f[3];  
  24. public:  
  25.    virtual void cc(){};  
  26. };  
  27.   
  28. int main()  
  29. {  
  30.     cout << "sizeof(VirtualInheritance):" << sizeof(VirtualInheritance) << endl;  
  31.     cout << "sizeof(sonClass1):" << sizeof(sonClass1) << endl;  
  32.     cout << "sizeof(sonClass2):" << sizeof(sonClass2) << endl;  
  33.   
  34.     return 0;  
  35. }  

  评注:此时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、普通、虚拟混合多继承,此时子类的大小为自身大小(若子类或普通父类有虚函数,则为成员变量+虚表指针大小;若都没虚函数,则就为成员变量大小),加上一个虚类指针大小,在加上虚拟父类的大小,在加上普通父类的大小(除虚表指针,因为它和子类公用一个虚表指针)。