首页 存档 技术 查看内容

C 继承、虚继承、虚函数类的大小问题

2018-3-30 13:00 |来自: 互联网 651 0

摘要: 一、真空类 class CNull{}; 长度:1 内存结构: ?? 评注:长度其实为0,这个字节作为内容没有意义,可能每次都不一样。 二、空类 class CNull2 { public: CNull2(){printf("Construct/n");} ~CNull2() ...

一、真空类

class CNull{};

长度:1


内存结构:


??

评注:长度其实为0,这个字节作为内容没有意义,可能每次都不一样。


二、空类

class CNull2\n{\npublic:\n    CNull2(){printf("Construct/n");}\n    ~CNull2(){printf("Desctruct/n");}\n    void Foo(){printf("Foo/n");}\n};

长度:1


内存结构:


??

评注:同真空类差不多,内部的成员函数并不会影响类大小。


三、简单类

class COneMember\n{\npublic:\n    COneMember(int iValue = 0){m_iOne = iValue;};\nprivate:\n    int m_iOne;\n};

长度:4


内存结构:


00 00 00 00 //m_iOne

评注:成员数据才影响类大小。


四、简单继承

class CTwoMember:public COneMember\n{\nprivate:\n    int m_iTwo;\n};

长度:8


内存结构:

00 00 00 00 //m_iOneCC CC CC CC //m_iTwo

评注:子类成员接在父类成员之后。


五、再继承

class CThreemember:public CTwoMember{public:    CThreemember(int iValue=10) {m_iThree = iValue;};private:    int m_iThree;};

长度:12


内存结构:长度:12

00 00 00 00 //m_iOneCC CC CC CC //m_iTwo0A 00 00 00 //m_iThree

评注:孙类成员接在子类之后,再再继承就依此类推了。


六、多重继承

class ClassA\n{\npublic:\n    ClassA(int iValue=1){m_iA = iValue;};\nprivate:\n    int m_iA;\n};\n \nclass ClassB\n{\npublic:\n    ClassB(int iValue=2){m_iB = iValue;};\nprivate:\n    int m_iB;\n};\n \nclass ClassC\n{\npublic:\n    ClassC(int iValue=3){m_iC = iValue;};\nprivate:\n    int m_iC;\n};\n \nclass CComplex :public ClassA, public ClassB, public ClassC\n{\npublic:\n    CComplex(int iValue=4){m_iComplex = iValue;};\nprivate:\n    int m_iComplex;\n};

长度:16


内存结构:


01 00 00 00  //A\n02 00 00 00  //B\n03 00 00 00  //C\n04 00 00 00  //Complex

评注:也是父类成员先出现在前边,我想这都足够好理解。


七、复杂一些的继承


不写代码了,怕读者看了眼花,改画图。


长度:32


内存结构:


01 00 00 00 //A\n02 00 00 00 //B\n03 00 00 00 //C\n04 00 00 00 //Complex\n00 00 00 00 //OneMember\nCC CC CC CC //TwoMember\n0A 00 00 00 //ThreeMember\n05 00 00 00 //VeryComplex

评注:还是把自己的成员放在最后。


只要没涉及到“虚”(Virtual),我想没什么难点,不巧的是“虚”正是我们要研究的内容。


八、趁热打铁,看“虚继承”

class CTwoMember:virtual public COneMember{private:    int m_iTwo;};

长度:12


内存结构:


E8 2F 42 00 //指针,指向一个关于偏移量的数组,且称之虚基类偏移量表指针CC CC CC CC // m_iTwo00 00 00 00 // m_iOne(虚基类数据成员)

评注:virtual让长度增加了4,其实是多了一个指针,关于这个指针,确实有些复杂,别的文章有具体分析,这里就不岔开具体讲了,可认为它指向一个关于虚基类偏移量的数组,偏移量是关于虚基类数据成员的偏移量。


九、“闭合”虚继承,看看效果



长度:24


内存结构:


14 30 42 00 //ClassB的虚基类偏移量表指针02 00 00 00 //m_iBC4 2F 42 00 //ClassC的虚基类偏移量表指针03 00 00 00 //m_iC04 00 00 00 //m_iComplex01 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 //指向虚函数表的指针(虚函数表后面简称“虚表”)\n \n00423100:(虚表)\n41 10 40 00 //指向虚函数Foo的指针\n \n00401041:\nE9 78 02 00 00 E9 C3 03 … //函数Foo的内容(看不懂)

评注:带虚函数的类长度就增加了4,这个4其实就是个指针,指向虚函数表的指针,上面这个例子中虚表只有一个函数指针,值就是“0x00401041”,指向的这个地址就是函数的入口了。


十二、继承带虚函数的类

class CVirtualDerived : public CVirtualNull\n{\npublic:\n    CVirtualDerived(){m_iVD=0xFF;};\n    ~CVirtualDerived(){};\nprivate:\n    int m_iVD;\n};

长度:8


内存结构:

3C 50 42 00 //虚表指针FF 00 00 00 //m_iVD 0042503C:(虚表)23 10 40 00 //指向虚函数Foo的指针,如果这时候创建一个CVirtualNull对象,会发现它的虚表的内容跟这个一样

评注:由于父类带了虚函数,子类就算没有显式声明虚函数,虚表还是存在的,虚表存放的位置跟父类不同,但内容是同的,也就是对父类虚表的复制。


十三、子类有新的虚函数

class CVirtualDerived: public CVirtualNull\n{\npublic:\n    CVirtualDerived(){m_iVD=0xFF;};\n    ~CVirtualDerived(){};\n    virtual void Foo2(){printf("Foo2/n");};\nprivate:\n    int m_iVD;\n};

长度:8


内存结构:


24 61 42 00 //虚表指针FF 00 00 00 //m_iVD 00426124:(虚表)23 10 40 0050 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:\n(不可实例化)\n \nCDerivePV:\n28 50 42 00 //虚表指针\n \n00425028:(虚表)\n5A 10 40 00 //指向Foo的函数指针

评注:带纯虚函数的类不可实例化,因此列不出其“内存结构”,由其派生类实现纯虚函数。我们可以看到CDerivePV虽然没有virtual声明,但由于其父类带virtual,所以还是继承了虚表,如果CDerivePV有子类,还是这个道理。


十五、虚函数类的多重继承


前面提到:(子类的虚表)不会因为增加了新的虚函数而多出另一张来,但如果有多重继承的话情况就不是这样了。下例中你将看到两张虚表。



大小:24


内存结构


F8 50 42 00 //虚表指针\n01 00 00 00 //m_iA\n02 00 00 00 //m_iB\nE8 50 42 00 //虚表指针\n03 00 00 00 //m_iC\n04 00 00 00 //m_iComplex\n \n004250F8:(虚表)\n5A 10 40 00 //FooA\n55 10 40 00 //FooB\n64 10 40 00 //FooComplex\n \n004250E8:(虚表)\n5F 10 40 00 //FooC

评注:子类的虚函数接在第一个基类的虚函数表的后面,所以B接在A后面,Complex接在B后面。基类依次出现,子类成员接在最后面,所以m_iComplex位于最后面。


十六、包含虚函数类的虚继承

class VirtualInheritance  \n{  \n    char k[3];  \npublic:  \n    virtual void aa(){};  \n};  \nclass sonClass1: public virtual VirtualInheritance  \n{  \n    char j[3];  \npublic:  \n    virtual void bb(){};  \n};  \nclass sonClass2: public virtual sonClass1  \n{  \n    char f[3];  \npublic:  \n    virtual void cc(){};  \n};  \n  \nint main()  \n{  \n    cout
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部