84 C++对象模型探索。数据语义学 - 继承多个类的时的数据布局问题。

此章节分析多继承问题,难点,但是非重点,实际开发中,多继承用的很少,容易被code review,可以不看。

我们要访问一个类对象中的成员
成员的定位是通过如下两个因素决定的:this指针(编译器会自动调整) ,以及该成员的偏移量,
这样this指针的调整,都需要编译器的介入来完成。

Teacher  继承 Teacher1,继承Teacher2,继承Teacher3

//多继承下的成员布局分析

class Teacher13base1 {
public:
	Teacher13base1() {
		cout << "Teacher13base1 gouzao this = " << this << endl;
	}
public:
	int base1age;

private:
	int base1privateage;
public:
	virtual void base1virtualfunc() {

	}
};

class Teacher13base2 {
public:
	Teacher13base2() {
		cout << "Teacher13base2 gouzao this = " << this << endl;
	}
public:
	int base2age;

private:
	int base2privateage;
public:
	virtual void base2virtualfunc() {

	}
};

class Teacher13base3 {
public:
	Teacher13base3() {
		cout << "Teacher13base3 gouzao this = " << this << endl;
	}
public:
	int base3age;

private:
	int base3privateage;
public:
	virtual void base3virtualfunc() {

	}
};

class Teacher13 :public Teacher13base1, public Teacher13base2, public Teacher13base3{
public:
	Teacher13() {
		cout << "Teacher13 gouzao this = " << this << endl;
	}
public:
	int age;

private:
	int privateage;
public:
	virtual void virtualfunc() {

	}
};

void main() {

	cout << sizeof(Teacher13base1) << endl;//12  Teacher13base1的vptr + 2ge int
	cout << sizeof(Teacher13base2) << endl;//12  Teacher13base2的vptr + 2ge int
	cout << sizeof(Teacher13base3) << endl;//12  Teacher13base3的vptr + 2ge int
	cout << sizeof(Teacher13) << endl;//44    8个 int = 32 + 3个vptr(Teacher13base1的vptr(Teacher13的vptr	) + Teacher13base2的vptr + Teacher13base3的vptr )

	printf("Teacher13::base1age的地址是%p\n", &Teacher13::base1age);
	printf("Teacher13::base2age的地址是%p\n", &Teacher13::base2age);
	printf("Teacher13::base3age的地址是%p\n", &Teacher13::base3age);
	printf("Teacher13::base1age的地址是%p\n", &Teacher13::age);
	Teacher13 tea;
	tea.base1age = 1;
	tea.base2age = 2;
	tea.base3age = 3;
	tea.age = 4;

	cout << "断点在这里" << endl;

}


12
12
12
44
Teacher13::base1age的地址是00000004
Teacher13::base2age的地址是00000004
Teacher13::base3age的地址是00000004
Teacher13::base1age的地址是00000024
Teacher13base1 gouzao this = 012FFB10
Teacher13base2 gouzao this = 012FFB1C
Teacher13base3 gouzao this = 012FFB28
Teacher13 gouzao this = 012FFB10
断点在这里

分析

0x012FFB10  f4 3e 9c 00 01 00 00 00 cc cc cc cc 04 3f 9c 00 02 00 00 00 cc cc cc cc 10 3f 9c 00  
0x012FFB2C  03 00 00 00 cc cc cc cc 04 00 00 00 cc cc cc cc

f4 3e 9c 00  Teacher13base1 的vptr指针的地址,也是Teacher13的vptr指针的地址

01 00 00 00  Teacher13base1.base1age

cc cc cc cc  Teacher13base1.base1privateage
 
04 3f 9c 00  Teacher13base2 的vptr指针的地址
 
02 00 00 00  Teacher13base2.base2age
 
cc cc cc cc  Teacher13base2.base2privateage
  
10 3f 9c 00  Teacher13base3 的vptr指针的地址
  
03 00 00 00  Teacher13base3.base3age
  
cc cc cc cc  Teacher13base3.base3privateage

04 00 00 00  teacher13.age

cc cc cc cc  teacher13.privateage

继承多个类的时候,最终子类和第一个继承的父类公用 vptr,this指针指向相同

其他父类的this指针都和tea不一样

其他父类的vptr都在原来的位置上。

那么根据上述的结论,如下的代码会怎么样呢?


	Teacher13 tea13;

	cout << "tea13的地址为:" << &tea13 << endl;

	Teacher13base2 *pbase2 = &tea13;
	cout << "pbase2的地址为:" << pbase2 << endl;

tea13的地址为:006BFB3C
pbase2的地址为:006BFB48

这个结论我们已经知道了,Teacher13base2 的 this指针会想下移动12字节,因此这两值不一样。

从编译器的角度分析,那么一定是在Teacher13base2 *pbase2 = &tea13这一行做了额外的事情。

//Teacher13base2 *pbase2 = (Teacher13base2*)(  ((char*)&tea13) + sizeof(Teacher13base1));

放开这一行测试结果如下:

tea13的地址为:00CFF82C
pbase2的地址为:00CFF838

总结上面的例子:

	Teacher13 tea13;

	cout << "tea13的地址为:" << &tea13 << endl;

	Teacher13base2 *pbase2 = &tea13;
	cout << "pbase2的地址为:" << pbase2 << endl;
	//站在编译器角度,是这么干的
	Teacher13base2 *pbase3 = (Teacher13base2*)(((char*)&tea13) + sizeof(Teacher13base1));
	cout << "pbase2的地址为:" << pbase3 << endl;
}

tea13的地址为:012FFA10
pbase2的地址为:012FFA1C
pbase2的地址为:012FFA1C

    //再来看一个特殊的

	//再来看一个特殊的

	Teacher13base2 *pbase4 = new Teacher13();//这句话的理解是:new Teacher13肯定是创建了44个字节的数据。但是this指针是Teacher13base2* 的,因此this指针是指向 内存模型的中间的位置
	//delete pbase4;//那么这样delete,相当于在内存模型的中间位置delete,会有runtime exception

	//那么要怎么删除呢?再转化成 teacher13 *,然后再delete

	Teacher13 * temppbase4 = (Teacher13 *)pbase4;
	delete temppbase4;

	//实际上编译器内部是这么干的
	//Teacher13 * temppbase5 = (Teacher13 *)((char *)pbase4 -sizeof(Teacher13base1));
	//delete temppbase5;

上述一些特殊的case,都是因为多继承引起的,多继承的时候,使用了非第一个继承的指针,指向子类对象,由于new 出来的是整个子类对象,但是this指针因为调整的问题,确不在最 子类对象的最上面。

建议少用多继承。

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

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

相关文章

向日葵企业“云策略”升级 支持Android 被控策略设置

此前&#xff0c;贝锐向日葵推出了适配PC企业客户端的云策略功能&#xff0c;这一功能支持管理平台统一修改设备设置&#xff0c;上万设备实时下发实时生效&#xff0c;很好的解决了当远程控制方案部署后&#xff0c;想要灵活调整配置需要逐台手工操作的痛点&#xff0c;大幅提…

Redis系列-数据结构篇

数据结构 string&#xff08;字符串&#xff09; redis的字符串是动态字符串&#xff0c;类似于ArrayList&#xff0c;采用预分配冗余空间的方式减少内存的频繁分配。 struct SDS<T>{ T capacity; T len; byte flags; byte[] content; } 当字符串比较短时&#xff0c…

跟着pink老师前端入门教程-day14

2.6 main 主体模块制作 HTML&#xff1a; <div class"w"><div class"main"><!-- 焦点图模块 --><div class"focus"><ul><li><img src"./images/banner_bg.png" alt""></li>…

提高 NFS Azure 文件共享性能

本文内容 适用于增加预读大小以提高读取吞吐量Nconnect另请参阅 本文介绍如何提高 NFS Azure 文件共享的性能。 适用于 展开表 文件共享类型SMBNFS标准文件共享 (GPv2)、LRS/ZRS 标准文件共享 (GPv2)、GRS/GZRS 高级文件共享 (FileStorage)、LRS/ZRS 增加预读大…

指针的深入理解(二)

这节主要复习函数指针 函数指针 函数指针的标志就是int (* ) (数据类型)&#xff0c; 是储存函数的地址的指针变量。函数名就是的首地址。我们平常使用的函数名就是函数的地址&#xff1a; 由此可以发现&#xff0c;我们可以通过函数的地址来使用函数。 那么我们就可以知道函…

什么是Vue Vue入门案例

一、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套 构建用户界面 的 渐进式 框架 Vue2官网&#xff1a;Vue.js 1.什么是构建用户界面 基于数据渲染出用户可以看到的界面 2.什么是渐进式 所谓渐进式就是循序渐进&#xff0c;不一定非得把V…

NC开发客户端(前端)连接启动失败can‘t connect to server, please wait

效果图 解决方法 IP地址和端口要对应 1-IP地址中间启动&#xff0c;肯定是这个127.0.0.1 2-端口号&#xff0c;要对应中间件启动在控制台输出的端口 或者是在home目录-》bin-》sysConfig.bat这里面的服务器&#xff0c; 里面可以看到对应启动ip地址和端口

2023年春秋杯网络安全联赛冬季赛_做题记录

可信计算 基于挑战码的双向认证1 可信计算赛题-双向认证挑战模式.docx 使用命令进行SSH登录上去 ssh player8.147.131.156 -p 18341 # 记得加上-p参数指定端口&#xff0c;不然默认的是22端口看见word文档的提示&#xff0c;先尝试一下 直接获得了flag1 web 魔术方…

Linux:理解信号量以及内核中的三种通信方式

文章目录 共享内存的通信速度消息队列msggetmsgsndmsgrcvmsgctl 信号量semgetsemctl 内核看待ipc资源单独设计的模块ipc资源的维护 理解信号量总结 本篇主要是基于共享内存&#xff0c;延伸出对于消息队列和信号量&#xff0c;再从内核的角度去看这三个模块实现进程间通信 共享…

SpringCloudStream整合MQ

目录 概念 快速搭建SCS环境 一秒切换MQ 组件 1. Binder 2. Binding 3. Message 分组消费 概念 Spring Cloud Stream&#xff08;SCS&#xff09; 的主要目标是一套代码&#xff0c;兼容所有MQ, 降低MQ的学习成本&#xff0c;提供一致性的编程模型&#xff0c;让开发者能更…

数据可视化练习

文章目录 试题示例 试题示例 绘制下图所示的表格 根据下表的数据&#xff0c;将班级名称一列作为x轴的刻度标签&#xff0c;将男生和女生两列的数据作为刻度标签对应的数值&#xff0c;使用bar()函数绘制下图所示的柱形图。 方式一 import numpy as np import matplotlib.p…

web自动化搞定文件上传

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

Spring 学习1

1、什么是Spring Spring 是一款主流的 Java EE 轻量级开源框架 &#xff0c;Spring 由“Spring 之父”Rod Johnson 提出并创立&#xff0c;其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言…

AI算力专题:算力系列之四-各省算力规划建设梳理-绿色低碳高质量发展-部署算力建设AI产业研究

今天分享的是AI算力系列深度研究报告&#xff1a;《AI算力专题&#xff1a;算力系列之四-各省算力规划建设梳理-绿色低碳高质量发展-部署算力建设AI产业研究》。 &#xff08;报告出品方&#xff1a;中泰证券&#xff09; 报告共计&#xff1a;40页 数据中心能耗情况 随着越…

java的面向对象编程(oop)——认识接口

前言&#xff1a; 打好基础&#xff0c;daydayup! 接口 接口概述 java提供一个关键字interface&#xff0c;用这个关键字可以定义出特殊结构&#xff1a;接口 接口格式&#xff1a; public interface 接口名{//成员变量&#xff08;常量&#xff09;//成员方法&#xff08;抽…

Dragons

题目链接&#xff1a; Problem - 230A - Codeforces 解题思路&#xff1a; 用结构体排序就好&#xff0c;从最小的开始比较&#xff0c;大于就加上奖励&#xff0c;小于输出NO 下面是c代码&#xff1a; #include<iostream> #include<algorithm> using namespac…

【2024.1.30练习】李白打酒加强版(25分)

题目描述 题目思路 在最多数据的情况下&#xff0c;有100个店100朵花&#xff0c;总情况为的天文数字&#xff0c;暴力枚举已经不可能实现&#xff0c;考虑使用动态规划解决问题。最后遇到的一定是花&#xff0c;所以思路更倾向于倒推。 建立二维数组&#xff0c;容易联想到为…

软件个性化选型:制造企业如何选择适合自身的工单管理系统-亿发

企业制造业是实体经济中非常重要和基础的组成部分&#xff0c;直接关系到国家经济的血脉。然而&#xff0c;传统制造业在生产与管理上所采用的老一套方法和经验已不再适应当下的发展需求。信息化、数字化和智能化被视为制造企业的必然趋势。要想在竞争激烈的市场中永立潮头&…

都2024年了,谁还在逛良品铺子?

作者 | 辰纹 来源 | 洞见新研社 2019年年初&#xff0c;良品铺子举办了一场高端零食战略发布会&#xff0c;当时还花重金请来顶流明星为品牌代言&#xff0c;在强化“高端零食”定位的同时&#xff0c;良品铺子坚定的表示&#xff0c;要“抛弃价格战”。 时任良品铺子董事长杨…

24小时涨粉10w+的AI小游戏-哄哄模拟器

近年来&#xff0c;随着chatGPT的爆火&#xff0c;一系列的AI应用应运而生。比如&#xff1a;AI绘画&#xff0c;AI写作等。今天我们来看看最近很火的一个AI小游戏-哄哄模拟器。 1. 试玩体验 这款游戏名叫“哄哄模拟器”&#xff0c;体验地址为&#xff1a;https://hong.grea…