• C++类对应的内存结构


    提示1:对“内存结构”表示有疑问或不解的,先参考:

    http://blog.csdn.net/guogangj/archive/2007/05/25/1625199.aspx

    本文使用的表示方法和VC6的Memory视图一致,即:左上表示低位。

     

    提示2:下文提到的“类大小”严格上来说是该类经过实例化的对象的大小。当然了,光研究长度的话,两者差别不大,因为:CClassA objA,sizeof(CClassA)和sizeof(objA)得到的结果都是一样的。

     

    一、真空类

    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位于最后面。

     

    本来还想看看更复杂些的情况,甚至包括虚继承和虚函数同时出现的多重多层继承情况,但确实有些复杂了,自己还有些找不到规律,所以准备之后再补充。

     

    from:http://blog.csdn.net/guogangj/article/details/2036785#comments

  • 相关阅读:
    [转]CentOS 修改yum源为国内源
    著名的镜像网站
    [译]LRTHW练习五——更多的变量及输出打印
    CentOS足迹一
    LRTHW笔记二
    线程的创建和运行(未完待续)
    比较当前时间和给定时间大小
    单独管理image
    Java 修改页面排序条件
    Java 如何使用radio button保存值
  • 原文地址:https://www.cnblogs.com/lidabo/p/3439131.html
Copyright © 2020-2023  润新知