优秀的编程知识分享平台

网站首页 > 技术文章 正文

一起探索C++类内存分布(c++类占用内存)

nanyue 2024-07-30 03:12:15 技术文章 5 ℃

一起探索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;所以说:虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(增加了更多的指针)


Tags:

最近发表
标签列表