zoukankan      html  css  js  c++  java
  • 继承、虚继承和虚函数表对类的大小的影响

    一、真空类

    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
     
    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、包含虚函数的多重普通、虚拟混合继承

    [cpp] view plain copy
     
    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、包含虚函数的多重虚拟继承

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

  • 相关阅读:
    Why Choose Jetty?
    Jetty 的工作原理以及与 Tomcat 的比较
    Tomcat设计模式
    Servlet 工作原理解析
    Tomcat 系统架构
    spring boot 打包方式 spring boot 整合mybaits REST services
    wireshark udp 序列号 User Datagram Protocol UDP
    Maven 的聚合(多模块)和 Parent 继承
    缓存策略 半自动化就是mybaitis只支持数据库查出的数据映射到pojo类上,而实体到数据库的映射需要自己编写sql语句实现,相较于hibernate这种完全自动化的框架我更喜欢mybatis
    Mybatis解决sql中like通配符模糊匹配 构造方法覆盖 mybits 增删改
  • 原文地址:https://www.cnblogs.com/zlcxbb/p/5854662.html
Copyright © 2011-2022 走看看