C++内存布局(二)

在《C++内存布局(一)》 中,我们介绍了C++内存布局的基本知识,本篇我们仍着重探讨C++类的内存布局,尤其是 多重继承钻石继承(菱形继承)场景下的虚函数表的情况。

一、多重继承

1.1 示例

class A
{
public:
    virtual void da(){MYTRACE();}
};

class B
{
public:
    virtual void db(){MYTRACE();}
};

class C: public A,  public B
{
public:
    virtual void dc(){MYTRACE();}
};

int main()
{
    A a;
    B b;
    C c;

    return 0;
}

在这里插入图片描述

C 中只有两张表
[A::vptr] 继承自基类A中的虚函数表不仅记录了class A的虚函数地址,同时记录了
class B中的虚函数地址,而且还记录了class C 自己定义的虚函数地址。另外还记录一些其他的关联信息。
[B:vptr] 只记录了class B中虚函数的地址

1.2 示例

class A
{
public:
    virtual void da(){MYTRACE();}
};

class B
{
public:
    virtual void db(){MYTRACE();}
};

 相比1.1中的代码示例,我们在这里交互继承顺序
class C: public B,  public A
{
public:
    virtual void dc(){MYTRACE();}
};


int main()
{
    A a;
    B b;
    C c;

    return 0;
}

在这里插入图片描述
好家伙,多重继承掉个顺序,这下子表的顺序也调整了。
看来,在多重继承中,如果继承的多个基类存在多张虚函数表,会按照继承顺序,优先选择第一个基类中的虚函数表作为[基表] (自创词汇,勿cue)将所有关联的虚函数信息记录在此表中。但是表的数量仍取决于基类中虚函数表的总数。

1.3 虚继承1

class A
{
public:
    virtual void da(){MYTRACE();}
};

class B
{
public:
    virtual void db(){MYTRACE();}
};

/// 虚继承自A
class C: public virtual A,  public B
{
public:
    virtual void dc(){MYTRACE();}
};


int main()
{
    A a;
    B b;
    C c;

    return 0;
}

在这里插入图片描述
这种情况(多重继承非全虚继承)下,还是只有两张虚函数表(取决于所有基类中虚函数表总数);不过有趣的是,这次竟然没有按照继承顺序优先选择【基表】,而是在未采用虚继承的class BB::vptr里优先记录了所有的虚函数地址及关联的一些信息。所以我们貌似总结出了一些规律:
虚继承 > 继承顺序

1.4 虚继承2

同样,在1.3的基础上,我们把虚继承换个顺序,看看结果怎么样

class A
{
public:
    virtual void da(){MYTRACE();}
};

class B
{
public:
    virtual void db(){MYTRACE();}
};

class C: public  A, virtual  public B
{
public:
    virtual void dc(){MYTRACE();}
};

int main()
{
    A a;
    B b;
    C c;

    return 0;
}

在这里插入图片描述
结果得到验证,与我们在1.3中得到规律的预期一致。表还是两张,在未采用虚继承的class AA::vptr里优先记录了所有的虚函数地址及关联的一些信息。

1.5 虚继承3 : 交换继承顺序

class A
{
public:
    virtual void da(){MYTRACE();}
};

class B
{
public:
    virtual void db(){MYTRACE();}
};

class C: virtual  public B, public  A
{
public:
    virtual void dc(){MYTRACE();}
};

int main()
{
    A a;
    B b;
    C c;
    return 0;
}

在这里插入图片描述
交换了顺序,与我们在1.4中得到的结果依然一致。可见虚继承 优于 继承顺序,在我们目前用于验证的VS2019 x64 的编译工具链上确实是这样的。

1.6 全虚继承

这次我们in-all 了,多重继承的全部改为虚继承,我们再来探究下。

class A
{
public:
    virtual void da(){MYTRACE();}
};

class B
{
public:
    virtual void db(){MYTRACE();}
};

class C: virtual  public A, virtual public  B
{
public:
    virtual void dc(){MYTRACE();}
    virtual void dc1(){MYTRACE();}
};

int main()
{
    A a;
    B b;
    C c;

    return 0;
}

在这里插入图片描述
喔嚯,这次大不同啊,大不同!在全部采用虚继承的情况下,编译器为class C创建了一张新的表C::vptr,用于记录class Aclass Bclass C中的虚函数地址以及其他关联信息。三张表啊三张表。

1.7 更加复杂一些的多重继承

class A
{
public:
    virtual void da(){MYTRACE();}
};

class B
{
public:
    virtual void db(){MYTRACE();}
};

class C: virtual  public A, virtual public  B
{
public:
    virtual void dc(){MYTRACE();}
    virtual void dc1(){MYTRACE();}
};


class D :  public C
{
};

int main()
{
    A a;
    B b;
    C c;
    D d;

    return 0;
}

在这里插入图片描述

我们看菱形继承到class D这儿,D的实例化对象d中包含了多张虚函数表,包含
A::vptr
B::vptr
C::vptr
C::A::vptr ⇒ 这里我只是用来表明层级关系,::在此并不表示作用域,别纠结
C::B::vptr

是不是很妙。

如果class D也定义了虚函数呢,会创建新的表么?如下,显然没有,D的虚函数地址被记录在了继承自CC::vptr下。
在这里插入图片描述

如果菱形继承采用虚继承呢,结果又会怎么样?
在这里插入图片描述

在这里插入图片描述
果如其然,虚继承情况下创建了一张新的表,用于单独记录class D 中虚函数地址。

二、菱形继承

2.1

class A
{
public:
    virtual void d(){MYTRACE();}
    virtual void da(){MYTRACE();}
};

class B : virtual public A
{
public:
    virtual void db(){MYTRACE();}
};

class C:  virtual  public  A
{
public:
    virtual void dc(){MYTRACE();}
};


/// 非虚继承
class D :  public B,  public C
{
public:
    virtual void dd(){}

};

int main()
{
    D d;
    d.d();

    return 0;
}

在这里插入图片描述

2.2

		A
	/	 	 \
B			  C
    \        /
		D
class A
{
public:
    virtual void d(){MYTRACE();}
    virtual void da(){MYTRACE();}
};

/// 菱形继承必须使用虚继承
class B : virtual public A
{
public:
    virtual void db(){MYTRACE();}
};

/// 菱形继承必须使用虚继承
class C:  virtual  public  A
{
public:
    virtual void dc(){MYTRACE();}
};

/// 全虚继承
class D :  virtual public B, virtual public C
{
public:
    virtual void dd(){}

};

int main()
{
    D d;
    d.d();

    return 0;
}

在这里插入图片描述

三、总结

  • 对于多重继承,且所有虚基类均不被虚继承的情况下,虚函数表(虚表指针vptr)的数量取决于所有继承的父类中分别包含的虚函数表的总数。如果子类也有自定义的虚函数,按照基类继承顺序的优先级, 此虚函数地址被记录在继承的第一个基类的虚函数表中;
  • 对于多重继承,且仅只有部分虚基类被虚继承的情况下,虚函数表(虚表指针vptr)的数量取决于所有继承的父类中分别包含的虚函数表的总数N。如果子类也有自定义的虚函数,按照基类继承顺序的优先级, 此虚函数地址被记录在继承的第一个被非虚继承的虚基类的虚函数表中,子类中包含的虚函数表总数N;如果第一个被非虚继承的虚基类不存在,则会为子类新建一张虚函数表vptr[],此时子类中包含的虚函数表总数N+1;
  • 对于多重继承,且所有虚基类均被虚继承的情况下,虚函数表(虚表指针vptr)的数量取决于所有继承的父类中分别包含的虚函数表的总数N。如果子类也有自定义的虚函数,那编译器会为子类单独创建一张虚函数表,用于记录子类中自己定义的虚函数地址。此时,子类中包含的虚函数表的总数为N+1
  • 菱形继承是一种特殊的多重继承,处于中间层的父类必须使用virtual虚继承,避免函数名重复导致编译错误;虚函数表及地址的记录规则与上面几条保持一致。

以上,属于个人总结,纰漏指出请不吝指正~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/258784.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW软件模拟氢燃料电池在车辆中的应用

LabVIEW软件模拟氢燃料电池在车辆中的应用 在追求可持续能源的时代,氢燃料电池在绿色经济中扮演着关键角色。本研究通过LabVIEW软件模拟和评估了氢燃料电池在车辆应用中的性能和效率。LabVIEW作为一个强大的模拟工具,能够动态模拟氢燃料电池系统在不同条…

js键盘事件keydown事件,防止重复触发,组合键的配合使用

js键盘事件keydown事件,防止重复触发 键盘事件类型主要有三种: keydown 、keypress(不建议使用,部分浏览器已放弃)和 keyup 。 添加普通键盘keydown事件 // 监听键盘按下事件document.addEventListener(keydown, function(event) {// 输出按…

3 python基本语法 - Dict 字典

Python 中字典(dict)是一种无序的、可变的序列,它的元素以“键值对(key-value)”的形式存储。相对地,列表(list)和元组(tuple)都是有序的序列,它们…

23、Web攻防——Python考点CTF与CMS-SSTI模板注入PYC反编译

文章目录 一、PYC文件二、SSTI 一、PYC文件 pyc文件:python文件编译后生成的字节码文件(byte code),pyc文件经过python解释器最终会生成机器码运行。因此pyc文件是可以跨平台部署的,类似java的.class文件,…

Vue-图片懒加载

实现图片懒加载可以使用vue-lazyload插件 npm 链接:vue-lazyload - npm (npmjs.com) 使用方法: 1. 安装vue-lazyload npm i vue-lazyload npm i vue-lazyload1.3.3 // 如果是vue2就需要安装1.3.3版本 2. 引入vue-lazyload并使用 可以在使用该插…

软件企业在什么情况下需要找第三方软件测试机构?如何收费?

近年来,随着软件行业的迅猛发展,软件企业对软件测试的需求也越来越大。为了保证软件的质量和稳定性,许多企业选择寻找第三方软件测试机构来进行软件测试。第三方软件测试机构是独立于软件开发企业的专业机构,主要从事软件测试和质…

每日一题 2828. 判别首字母缩略词(简单)

简单题,就不多写了 class Solution:def isAcronym(self, words: List[str], s: str) -> bool:if len(words) ! len(s):return Falsefor i in range(len(words)):if words[i][0] ! s[i]:return Falsereturn True

栈(stack)

栈(stack)是一种用于存储数据的简单数据结构,与链表和顺序表很相似,最大的区别在于数据的存取操作。栈的插入和删除操作只允许在一端执行,因此把允许操作的一端称为栈顶,不允许操作的称为栈底。插入元素称为…

轻度听力损失的儿童需要早期干预吗?

一些宝宝在做听力筛查时总是不通过,进一步听力诊断发现宝宝有轻度的听力损失,刚知道这个消息时,家长可担心了,总想着宝宝是不是听不到啊?但是一段时间后,有些家长又会忽略宝宝的听力问题,因为部…

7款创意性前端源码特效资源分享(附在线预览效果)

分享7款非常不错炫酷的前端特效源码 其中包含css动画特效、js原生特效、svg特效等 下面我会给出特效样式图或演示效果图 但你也可以点击在线预览查看源码的最终展示效果及下载源码资源 CSS绘制iPhone 14带动态岛 纯CSS绘制iPhone 14带动态岛模型 运行初始化时还附带出场动画 …

防冻水表是什么?

防冻水表是一种特殊类型的水表,它主要用于防止水管和设备在寒冷的冬季中受到冻结和损坏的情况。在冷却系统中使用防冻水表可以有效地监测和控制液体的温度,从而保护系统的正常运行。 防冻水表通常由温度传感器、控制器和显示器组成。温度传感器负责测量液…

阿里云k8s集群搭建

文章目录 一、安装前准备1.环境2.k8s集群规划 二、k8s 安装1. centos基础设置2. docker 安装3. k8s安装3.1 添加阿里云 yum 源3.2 安装 kubeadm、kubelet、kubectl3.3 部署 Kubernetes Master3.4 加入 Kubernetes Node3.5 部署 CNI 网络插件3.6 测试 kubernetes 集群 一、安装前…

鸿蒙Harmony4.0开发-ArkTS基础知识运用

概念 1.渲染控制语法: 条件渲染:使用if/else进行条件渲染。 Column() {if (this.count > 0) {Text(count is positive)} }循环渲染:开发框架提供循环渲染(ForEach组件)来迭代数组,并为每个数组项创建…

abpvnext框架的项目部署到linux arm64版的docker中

参考: windows10下安装的docker 导出镜像到另一个电脑_docker镜像拷贝另一台机器的镜像-CSDN博客 前提条件: 1、vs2022,我的电脑本机安装有windows版docker desktop 。 2、linux中已经安装好docker,安装了sftp。这部分可以自行…

layui 树组件tree 通过API获取数据

一、简单 var treedata[];tree.render({elem: #addLeftType,id: demoId,data: treedata,showCheckbox: true,oncheck: function(obj){console.log(obj.data); // 得到当前点击的节点数据console.log(obj.checked); // 节点是否被选中console.log(obj.elem); // 得到当前节点元素…

模拟组建网络的过程

DNS是域名系统,作用是将域名解析成ip地址 要求 1.使用172.16.0.0网段组建网络 2.使用3台pc,可以配置DHCP服务自动分配ip 3.添加两个网站服务器 第一台是www.taobao.com 第二台www.jd.com 他们可以通过DNS服务器为客户解析域名 172开头的是B类ip地…

获取税率GET_TAX_PERCENTAGE

FTXP查看税码 GET_TAX_PERCENTAGE CALL FUNCTION GET_TAX_PERCENTAGEEXPORTINGaland ls_lfa1-land1datab sy-datummwskz ps_out-mwskztxjcd MWVS * EXPORT TABLESt_ftaxp lt_ftaxp.READ TABLE lt_ftaxp INTO DATA(ls_ftaxp) INDEX 1.IF sy-subrc 0.cs_po-…

javascript_1

3) string ⭐️ js 字符串三种写法 let a "hello"; // 双引号 let b "world"; // 单引号 let c hello; // 反引号 html 代码如下&#xff0c;用 java 和 js 中的字符串如何表示&#xff1f; <a href"1.html">超链接</a> …

年终汇报这么写,升值加薪必有你!

#01 你这么能干&#xff0c; 老板知道吗&#xff1f; — 打工人最怕什么&#xff1f; 最怕你忙前忙后&#xff0c;干活一大堆&#xff0c;气出一身结节&#xff0c;锅还没少背&#xff0c;最后升职加薪没有你&#xff0c;出国旅游不带你&#xff1b;更怕你日常996&#xf…