一起探索C++类内存分布

666 阅读2分钟

一起探索C++类内存分布

C++ 类中内存分布具体是怎么样,尤其是C++中含有继承、虚函数、虚拟继承以及菱形继承等等情况下。

由于在linux下没有windows下显示直观,我们采用vs2015进行调试。

  • 部署环境

    我们在 属性->C/C++ ->命令行 -> /d1 reportSingleClassLayoutXXX ,XXX表示类名;

  • 单个基础类

    class Base{private: int a; int b;public: void test();};
    

    内存分布:

    class Base size(8):   +-- -   0 | a   4 | b   +-- -
    

    总结:我们发现普通类的内存分布是根据声明的顺序进行的,成员函数不占用内存。

  • 基础类+继承类

    class Base{ int a; int b;public: void test();};class Divide :public Base{public: void run();private: int c; int d;};
    

    内存分布:

    class Divide size(16) :   +-- -   0 | +-- - (base class Base)   0 | | a   4 | | b | +-- -   8 | c  12 | d   +-- -
    

    总结:根据内存分布,我们发现普通继承类,内存分布也是按照声明的顺序进行的,成员函数不占用内存;类的顺序是先基类,后子类。

  • 含有虚函数的基类

    class Base{ int a; int b;public: void test(); virtual void run();};
    

    内存分布:

    class Base size(12) :   +-- -   0 | {vfptr}   4 | a   8 | b   +-- -  Base::$vftable@: | &Base_meta | 0   0 | &Base::run
    

    总结:带有虚函数的内存分布分为两部分,一部分是内存分布,一部分是虚表;我们从最上面发现,vfptr是放在了内存开始处,然后才是成员变量;虚函数run前面表示这个虚函数的序号为0

  • 含有虚函数的基类+继承子类

    class Base{ int a; int b;public: void test(); virtual void run();};class Divide :public Base{public: void DivideFun(); virtual void run();private: int c; int d;};
    

    内存分布:

    class Divide size(20) :   +-- -   0 | +-- - (base class Base)   0 | | {vfptr}   4 | | a   8 | | b | +-- -  12 | c  16 | d   +-- -  Divide::$vftable@: | &Divide_meta | 0   0 | &Divide::run
    

    总结:我们发现继承类,虚表只有一个,还是在内存开始处,内存排布顺序与普通继承类是一致的;

  • 含有虚函数的基类+继承子类(多增加一个虚函数)

    class Base{ int a; int b;public: void test(); virtual void run();};class Divide :public Base{public: void DivideFun(); virtual void run(); virtual void DivideRun();private: int c; int d;};
    

    内存分布:

    class Divide size(20) :   +-- -   0 | +-- - (base class Base)   0 | | {vfptr}   4 | | a   8 | | b | +-- -  12 | c  16 | d   +-- -  Divide::$vftable@: | &Divide_meta | 0   0 | &Divide::run   1 | &Divide::DivideRun
    

    总结:虚表还是继承于基类,在虚表部分多了DivideRun序号为1的虚函数;

  • 多重继承

    class Base{ int a; int b;public: virtual void run();};class Divide1 :public Base{public: virtual void run();private: int c;};class Divide2 :public Base{public: virtual void run();private: int d;};class Divide :public Divide1, Divide2{public: virtual void run();private: int d;};
    

    内存分布:

    class Divide1 size(16) :   +-- -   0 | +-- - (base class Base)   0 | | {vfptr} 4 | | a 8 | | b| +-- - 12 | c  +-- - Divide1::$vftable@:| &Divide1_meta| 0  0 | &Divide1::run Divide1::run this adjustor: 0 class Divide2 size(16) :  +-- -   0 | +-- - (base class Base)   0 | | {vfptr}  4 | | a  8 | | b| +-- - 12 | d  +-- - Divide2::$vftable@:| &Divide2_meta| 0  0 | &Divide2::run Divide2::run this adjustor: 0 class Divide size(36) :  +-- -   0 | +-- - (base class Divide1)   0 | | +-- - (base class Base)   0 | | | {vfptr} 4 | | | a 8 | | | b| | +-- - 12 | | c| +-- -  | +-- - (base class Divide2)  | | +-- - (base class Base)  | | | {vfptr}  | | | a  | | | b| | +-- -  | | d| +-- -  | d  +-- - Divide::$vftable@Divide1@:| &Divide_meta| 0  0 | &Divide::run Divide::$vftable@Divide2@:| -16  0 | &thunk: this -= 16; goto Divide::run Divide::run this adjustor: 0
    

    总结:主要看最后一个Divide类,内存排列顺序先是Divide1,后是Divide2,在Divide1和Divide2中各有一份虚表;

  • 虚拟继承(菱形继承)

    class Base{ int a; int b;public: virtual void run();};class Divide1 :virtual public Base{public: virtual void run();private: int c;};class Divide2 :virtual public Base{public: virtual void run();private: int d;};class Divide :public Divide1, Divide2{public: virtual void run();private: int d;};
    

    内存分布:

    class Divide1 size(20) :    +-- -    0 | {vbptr}   4 | c   +-- -   +-- - (virtual base Base)   8 | {vfptr}  12 | a  16 | b   +-- -  Divide1::$vbtable@:   0 | 0   1 | 8 (Divide1d(Divide1 + 0)Base)  Divide1::$vftable@: | -8   0 | &Divide1::run  Divide1::run this adjustor: 8  vbi:    class  offset o.vbptr  o.vbte fVtorDisp              Base       8       0       4 0  class Divide2 size(20) :   +-- -   0 | {vbptr}   4 | d   +-- -   +-- - (virtual base Base)   8 | {vfptr}  12 | a  16 | b   +-- -  Divide2::$vbtable@:   0 | 0   1 | 8 (Divide2d(Divide2 + 0)Base)  Divide2::$vftable@: | -8   0 | &Divide2::run  Divide2::run this adjustor: 8  vbi:    class  offset o.vbptr  o.vbte fVtorDisp              Base       8       0       4 0  class Divide size(32) :   +-- -   0 | +-- - (base class Divide1)   0 | | {vbptr}   4 | | c | +-- -   8 | +-- - (base class Divide2)   8 | | {vbptr}  12 | | d | +-- -  16 | d   +-- -   +-- - (virtual base Base)  20 | {vfptr}  24 | a  28 | b   +-- -  Divide::$vbtable@Divide1@:   0 | 0   1 | 20 (Divided(Divide1 + 0)Base)  Divide::$vbtable@Divide2@:   0 | 0   1 | 12 (Divided(Divide2 + 0)Base)  Divide::$vftable@: | -20   0 | &Divide::run
    

    总结:通过内存分布可知,Divide1Divide2都是两个虚表,Divide中却是成了3个虚表,只有一份base;所以说:虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(增加了更多的需指针)

首发:微信公众号《后台服务器开发》