C++类内存分布

转自:http://www.cnblogs.com/jerry19880126/p/3616999.html
先写下总结,通过总结下面的例子,你就会明白总结了。

下面总结一下:

1、虚基类指针和虚函数指针是可以继承的

2. 虚函数指针来源于父类或者自己是第一个声明虚函数的类(前提是自己没有继承虚函数指针,如果继承了,就不会产生新的虚函数指针),那么子类将父类的虚函数指针继承下来,并指向自身的虚表(发生在对象构造时,自己新建的表,不是父类的)。有多少个虚函数,虚函数表里面的项就会有多少。多重继承时,可能存在多个的基类虚函数表与虚函数指针;

3、如果是多重继承的情况下,子类中含有多个来自父类的虚函数表,子类新写了一个虚函数,那么该虚函数的地址,只会添加在第一个继承的父类的虚函数指针指向的表的尾部。如果重写了父类的虚函数,那么含有该虚函数地址的多个虚函数表都会更改地址。

(上面图中和真实的内存情况不对应,虚函数指针不是这么紧密排列在一起的,但是原理是对的)

4. 如果是虚继承,那么子类就会产生(自己的)虚基类指针指向其虚基类表(多重虚继承的话,也只产生一个),虚基类表包括其继承体系上的所有被虚继承的类。(当然其内存中也会继承其父类的虚基类指针和虚基类表)

书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别是在继承、虚函数存在的情况下。

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

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

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

1 class Base
2 {
3     int a;
4     int b;
5 public:
6     void CommonFunction();
7 };

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

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

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

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

1 class DerivedClass: public Base
2 {
3     int c;
4 public:
5     void DerivedCommonFunction();
6 };

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

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

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

1 class Base
2 {
3     int a;
4     int b;
5 public:
6     void CommonFunction();
7     void virtual VirtualFunction();
8 };

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

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

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

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

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

1 class DerivedClass: public Base
2 {
3     int c;
4 public:
5     void DerivedCommonFunction();
6     void virtual VirtualFunction();
7 };

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

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

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

1 class DerivedClass1 : public Base
2 {
3     int c;
4 public:
5     void DerivedCommonFunction();
6     void virtual VirtualFunction2();
7 };

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

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

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

1 class DerivedClass1 : public Base
2 {
3     int c;
4 public:
5     void DerivedCommonFunction();
6     void virtual VirtualFunction();
7     void virtual VirtualFunction2();
8 };

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

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

 1 class Base2 {3     int a;4     int b;5 public:6     void CommonFunction();7     void virtual VirtualFunction();8 };9
10
11 class DerivedClass1: public Base
12 {
13     int c;
14 public:
15     void DerivedCommonFunction();
16     void virtual VirtualFunction();
17 };
18
19 class DerivedClass2 : public Base
20 {
21     int d;
22 public:
23     void DerivedCommonFunction();
24     void virtual VirtualFunction();
25 };
26
27 class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
28 {
29     int e;
30 public:
31     void DerivedDerivedCommonFunction();
32     void virtual VirtualFunction();
33 };

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

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

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

DerivedClass2的情况是类似于DerivedClass1的。

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

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

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

 1 class DerivedClass1: virtual public Base2 {3     int c;4 public:5     void DerivedCommonFunction();6     void virtual VirtualFunction();7 };8 9 class DerivedClass2 : virtual public Base
10 {
11     int d;
12 public:
13     void DerivedCommonFunction();
14     void virtual VirtualFunction();
15 };
16
17 class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2
18 {
19     int e;
20 public:
21     void DerivedDerivedCommonFunction();
22     void virtual VirtualFunction();
23 };

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}在内存中的偏移。

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

当如下情况时:

class A
{
public:int a;
};
class B:virtual public A
{
public:int b;};
class C:virtual  public B
{
public:int c;};

  内存结构如下:

如图所示,存在两个虚基表,一个是属于C本身的其中也包含了A和B的,一个是其父类B 的,只含有A的

在看一个例子

class A
{
public:int a;
};
class B:virtual public A
{
public:int b;};
class C:virtual public B
{
public:int c;};class D: virtual public B
{
public:int d;
};
class E:public C,public D
{
public:int e;
};

  

对于多重虚继承的情况

 1 class A
 2 {
 3
 4 public:
 5
 6     int a;
 7
 8 };
 9
10 class B:virtual public A
11
12 {
13
14 public:
15
16     int b;
17
18
19
20 };
21
22 class C:virtual public A
23
24 {
25
26 public:
27
28     int c;
29
30
31
32 };
33
34
35
36 class D: virtual public B,virtual public C
37
38 {
39
40 public:
41
42     int d;
43
44 };

上面的情况说明了,多重虚继承也只产生一个虚基类指针,和一个虚基类表,虚基类表包括了继承体系中所有被虚继承的类。

转载于:https://www.cnblogs.com/acSzz/p/5664922.html

C++ 类的内存分布相关推荐

  1. VS查看C++类的内存分布、初步了解类的内存分布

    在VC++项目属性,如下图红线位置,输入 /d1 reportAllClassLayout ,确定: 之后,编译代码,即可查看类的内存分布: 写一个简单C++类,生成,在输出窗口可看到如下: 普通类的 ...

  2. C++类内存分布+钻石模型的解决方法

    C++类内存分布 #include<iostream> using namespace std; class Base {private:int val;public:Base(int i ...

  3. VS中查看子类对象内存分布的方法

    文章目录 1 VS中查看子类对象内存分布的方法 1 VS中查看子类对象内存分布的方法 设置vs编译器: 在命令行中添加选项(打印指定类的内存分布):/d1 reportSingleClassLayou ...

  4. 内存首地址为1000h_C++虚继承,菱形继承,内存分布

    前言 在叙述C++虚继承之前,我先给大家抛出一个问题.例如现在有4个类,分别是class A, class B, class C, class D.它们的关系如下图. 如上如所示,class B和cl ...

  5. 【C++】 类的内存对齐、虚函数表

      本文分为以下几个部分内容: 什么是内存对齐,为什么要内存对齐 C++的空类,以及没有虚函数和非静态变量的类 C++类的内存分布(成员变量) C++类的内存分布(虚函数) 一个类的情况 继承关系中的 ...

  6. C++类内存分布——深度理解继承与虚函数

    1.前言与准备 工欲善其事,必先利其器,我们先用好Visual Studio工具,像下面这样一步一步来:       先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 report ...

  7. 内存分布malloc/calloc/realloc/free/new/delete、内存泄露、String模板、浅拷贝与深拷贝以及模拟string类的实现

    内存分布 一.C语言中的动态内存管理方式:malloc/calloc/realloc和free 1.malloc: 从堆上获得指定字节的内存空间,函数声明:void *malloc (int n); ...

  8. 【c++内存分布系列】单独一个类

    首先要明确类型本身是没有具体地址的,它是为了给编译器生成相应对象提供依据.只有编译器生成的对象才有明确的地址. 一.空类 形如下面的类A,类里没有任何成员变量,类的sizeof值为1. #includ ...

  9. arm32 linux 内存分布,gcc代码反汇编查看内存分布[2]: arm-linux-gcc

    arm-none-linux-gnueabi-gcc -v gcc version 4.4.1 (Sourcery G++ Lite 2010q1-202) 重点: 代码中的内存分配, 地址从低到高: ...

最新文章

  1. EBS form日历可选范围设置(calendar.setup )介绍
  2. TP-LINK、迅捷等品牌路由器存在质量问题
  3. linux卸载rpm包
  4. jquery 常见选择器详解3
  5. 网络配置及shell基础
  6. 化工原理 蒸馏(下)
  7. HYSBZ 1588 营业额统计 平衡二叉树模板
  8. 技术管理规划-如何规划团队的架构
  9. 接口500什么原因_80%小餐饮店几乎都“活“不过500天,为什么?都在这5个原因里...
  10. Java Map 自定义排序
  11. linux与s7-300,Siemens SIMATIC S7-300硬编码凭证安全限制绕过漏洞
  12. AS/400开发经验点滴(六)如何制作下拉菜单
  13. 概率论与数理统计期末考试题及答案
  14. 计算机函数两个表格找相同,wps筛选出两个表格中的重复项(countif 函数简单使用)【已解决】...
  15. 植物大战僵尸:代码实现无限阳光
  16. stream根据某个字段去重(对象的某个字段去重)
  17. 2022年最新前端面试题、高频面试题、面试题总结
  18. WEB前端包含哪些内容
  19. 怎么关闭breeno语音
  20. style 对象 微信小程序_微信小程序中一些JS常识

热门文章

  1. WEB安全,SQL注入漏洞的加固代码汇总
  2. 田志刚:要你共享,但不告诉你共享什么
  3. Solidity基础入门知识(十)函数的访问权限和可见性
  4. 自行车车把会吧车刮坏吗_花10分钟即可开始使用车把
  5. javascript 代码_代码简介:2016年JavaScript的现状
  6. 【Postman】6 Postman 发送post请求-Json格式
  7. novaclient的api调用流程与开发
  8. 第六章:内核数据结构
  9. windows下配置redis集群,启动节点报错:createing server TCP listening socket *:7000:listen:Unknown error...
  10. 在Kubernetes v1.8中使用RBAC