纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

C++类内存分布 解析C++类内存分布

lsgxeva   2021-06-09 我要评论
想了解解析C++类内存分布的相关内容吗lsgxeva在本文为您仔细讲解C++类内存分布的相关知识和一些Code实例欢迎阅读和指正我们先划重点:c++,内存下面大家一起来学习吧。

工欲善其事必先利其器我们先用好Visual Studio工具像下面这样一步一步来:

先选择左侧的C/C++->命令行然后在其他选项这里写上/d1 reportAllClassLayout它可以看到所有相关类的内存布局如果写上/d1 reportSingleClassLayoutXXX(XXX为类名)则只会打出指定类XXX的内存布局。近期的VS版本都支持这样配置。

下面可以定义一个类像下面这样:

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

然后编译一下可以看到输出框里面有这样的排布:

这里不想花精力在内存对齐因素上所以成员变量都设为int型。

从这里可以看到普通类的排布方式成员变量依据声明的顺序进行排列(类内偏移为0开始)成员函数不占内存空间。

再看下继承往后面添加如下代码:

class DerivedClass: public Base
{
    int c;
public:
    void DerivedCommonFunction();
};

编译然后看到如下的内存分布(父类的内存分布不变这里只讨论子类成员变量的内存分布):

可以看到子类继承了父类的成员变量在内存排布上先是排布了父类的成员变量接着排布子类的成员变量同样成员函数不占字节。

下面给基类加上虚函数暂时注释掉DerivedClass看一下这时的内存排布:

class Base
{
    int a;
    int b;
public:
    void CommonFunction();
    void virtual VirtualFunction();
};

这个内存结构图分成了两个部分上面是内存分布下面是虚表我们逐个看。VS所带编译器是把虚表指针放在了内存的开始处(0地址偏移)然后再是成员变量;下面生成了虚表紧跟在&Base1_meta后面的0表示这张虚表对应的虚指针在内存中的分布下面列出了虚函数左侧的0是这个虚函数的序号这里只有一个虚函数所以只有一项如果有多个虚函数会有序号为1为2的虚函数列出来。

编译器是在构造函数创建这个虚表指针以及虚表的。

那么编译器是如何利用虚表指针与虚表来实现多态的呢?是这样的当创建一个含有虚函数的父类的对象时编译器在对象构造时将虚表指针指向父类的虚函数;同样当创建子类的对象时编译器在构造函数里将虚表指针(子类只有一个虚表指针它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。

所以如果是调用Base *p = new Derived();生成的是子类的对象在构造时子类对象的虚指针指向的是子类的虚表接着由Derived*到Base*的转换并没有改变虚表指针所以这时候p->VirtualFunction实际上是p->vfptr->VirtualFunction它在构造的时候就已经指向了子类的VirtualFunction所以调用的是子类的虚函数这就是多态了。

下面加上子类并在子类中添加虚函数像下面这样:

class DerivedClass: public Base
{
    int c;
public:
    void DerivedCommonFunction();
    void virtual VirtualFunction();
};

可以看到子类内存的排布如下:

上半部是内存分布可以看到虚表指针被继承了且仍位于内存排布的起始处下面是父类的成员变量a和b最后是子类的成员变量c注意虚表指针只有一个子类并没有再生成虚表指针了;下半部的虚表情况与父类是一样的。

我们把子类换个代码像这样:

class DerivedClass1 : public Base
{
    int c;
public:
    void DerivedCommonFunction();
    void virtual VirtualFunction2();
};

注意到这时我们并没有覆写父类的虚方法而是重声明了一个新的子类虚方法内存分布如下:

还是只有一个虚表指针但是下方虚表的内容变化了虚表的0号是父类的VirtualFunction而1号放的是子类的VirtualFunction2。也就是说如果定义了DerivedClass的对象那么在构造时虚表指针就会指向这个虚表以后如果调用的是VirtualFunction那么会从父类中寻找对应的虚函数如果调用的是VirtualFunction2那么会从子类中寻找对应的虚函数。

我们再改造一下子类像这样:

class DerivedClass1 : public Base
{
    int c;
public:
    void DerivedCommonFunction();
    void virtual VirtualFunction();
    void virtual VirtualFunction2();
};

我们既覆写父类的虚函数也有新添的虚函数那么可以料想的到是下面的这种内存分布:

下面来讨论多重继承代码如下:

class Base
{
    int a;
    int b;
public:
    void CommonFunction();
    void virtual VirtualFunction();
};


class DerivedClass1: public Base
{
    int c;
public:
    void DerivedCommonFunction();
    void virtual VirtualFunction();
};

class DerivedClass2 : public Base
{
    int d;
public:
    void DerivedCommonFunction();
    void virtual VirtualFunction();
};

class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
    int e;
public:
    void DerivedDerivedCommonFunction();
    void virtual VirtualFunction();
};

内存分布从父类到子类依次如下:

Base中有一个虚表指针地址偏移为0

DerivedClass1继承了Base内存排布是先父类后子类。

DerivedClass2的情况是类似于DerivedClass1的。

下面我们重点看看这个类DerivedDerivedClass由外向内看它并列地排布着继承而来的两个父类DerivedClass1与DerivedClass2还有自身的成员变量e。DerivedClass1包含了它的成员变量c以及BaseBase有一个0地址偏移的虚表指针然后是成员变量a和b;DerivedClass2的内存排布类似于DerivedClass1注意到DerivedClass2里面竟然也有一份Base。

这里有两份虚表了分别针对DerivedClass1与DerivedClass2在&DerivedDericedClass_meta下方的数字是首地址偏移量靠下面的虚表的那个-16表示指向这个虚表的虚指针的内存偏移这正是DerivedClass2中的{vfptr}在DerivedDerivedClass的内存偏移。

如果采用虚继承像下面这样:

class DerivedClass1: virtual public Base
{
    int c;
public:
    void DerivedCommonFunction();
    void virtual VirtualFunction();
};

class DerivedClass2 : virtual public Base
{
    int d;
public:
    void DerivedCommonFunction();
    void virtual VirtualFunction();
};

class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2
{
    int e;
public:
    void DerivedDerivedCommonFunction();
    void virtual VirtualFunction();
};

Base类没有变化但往下看:

DerivedClass1就已经有变化了原来是先排虚表指针与Base成员变量vfptr位于0地址偏移处;但现在有两个虚表指针了一个是vbptr另一个是vfptr。vbptr是这个DerivedClass1对应的虚表指针它指向DerivedClass1的虚表vbtable另一个vfptr是虚基类表对应的虚指针它指向vftable。

下面列出了两张虚表第一张表是vbptr指向的表8表示{vbptr}与{vfptr}的偏移;第二张表是vfptr指向的表-8指明了这张表所对应的虚指针位于内存的偏移量。

DerivedClass2的内存分布类似于DerivedClass1同样会有两个虚指针分别指向两张虚表(第二张是虚基类表)。

下面来仔细看一下DerivedDerivedClass的内存分布这里面有三个虚指针了但base却只有一份。第一张虚表是内含DerivedClass1的20表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离第二张虚表是内含DerivedClass2的12表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离最后一张表是虚基表-20指明了它对应的虚指针{vfptr}在内存中的偏移。

虚继承的作用是减少了对基类的重复代价是增加了虚表指针的负担(更多的虚表指针)。

下面总结一下(当基类有虚函数时):

1. 每个类都有虚指针和虚表;

2. 如果不是虚继承那么子类将父类的虚指针继承下来并指向自身的虚表(发生在对象构造时)。有多少个虚函数虚表里面的项就会有多少。多重继承时可能存在多个的基类虚表与虚指针;

3. 如果是虚继承那么子类会有两份虚指针一份指向自己的虚表另一份指向虚基表多重继承时虚基表与虚基表指针有且只有一份。


相关文章

猜您喜欢

  • Go timer 调度 Go timer怎样调度

    想了解Go timer怎样调度的相关内容吗haohongfan在本文为您仔细讲解Go timer 调度 的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Go,timer,调度,Go,timer下面大家一起来学习吧。..
  • @NotBlank不生效 怎样解决@NotBlank不生效的问题

    想了解怎样解决@NotBlank不生效的问题的相关内容吗Eddie-Wang在本文为您仔细讲解@NotBlank不生效的相关知识和一些Code实例欢迎阅读和指正我们先划重点:@NotBlank不生效,@NotBlank下面大家一起来学习吧。..

网友评论

Copyright 2020 www.Musicdownload3mp.com 【飞音下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式