首页 > 编程语言 >深度解读《深度探索C++对象模型》之数据成员的存取效率分析(三)

深度解读《深度探索C++对象模型》之数据成员的存取效率分析(三)

时间:2024-04-22 16:12:13浏览次数:30  
标签:存取 printf mov C++ rbp p3d 深度 Point3d Point2d

接下来我将持续更新“深度解读《深度探索C++对象模型》”系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文和全部的文章列表。

前面两篇请通过这里查看:

深度解读《深度探索C++对象模型》之数据成员的存取效率分析(一)

深度解读《深度探索C++对象模型》之数据成员的存取效率分析(二)

这一节讲解具体继承的情况,具体继承也叫非虚继承(针对虚继承而言),分为两种情况讨论:单一继承和多重继承。

单一继承

在上面的例子中,所有的数据都封装在一个类中,但有时可能由于业务的需要,需要拆分成多个类,然后每个类之间具有继承关系,比如可能是这样的定义:

class Point {
	int x;
};
class Point2d: public Point {
	int y;
};
class Point3d: public Point2d {
	int z;
};

对于这样的单一继承关系,在前面的文章《深度解读《深度探索C++对象模型》之C++对象的内存布局》中已经分析过了。一般而言,Point3d类的内存布局跟独立声明的类的内存布局没什么差别,除非在某些情况下,编译器为了内存对齐而进行填充,造成空间占用上会变大的情况,但对于存取效率而言没什么影响,因为在编译期间就已经确定好了它们的偏移值。完善上面的例子,在main函数中定义Point3d的对象,然后访问各个成员,看看对应的汇编代码。

int main() {
    printf("&Point2d::y = %d\n", &Point2d::y);
    printf("&Point3d::y = %d\n", &Point3d::y);
    Point3d p3d;
    p3d.x = p3d.y = p3d.z = 1;

    return 0;
}

上面两行打印代码输出的都是4,再看看第5行代码对应的汇编代码:

mov     dword ptr [rbp - 8], 1
mov     dword ptr [rbp - 12], 1
mov     dword ptr [rbp - 16], 1

生成的汇编代码跟独立类的汇编代码没有区别,这说明单一继承的存取效率跟没有继承关系的类的存取效率是一样的。

多重继承

或许业务需要,继承关系不是上面的单一继承关系,而是需要改成多重继承关系,多重继承下对象的存取效率是否会受影响?我们来看一个具体的例子:

#include <cstdio>

class Point {
public:
    int x;
};
class Point2d {
public:
    int y;
};
class Point3d: public Point, public Point2d {
public:
    int z;
};

int main() {
    printf("&Point2d::y = %d\n", &Point2d::y);
    printf("&Point3d::x = %d\n", &Point3d::x);
    printf("&Point3d::y = %d\n", &Point3d::y);
    printf("&Point3d::z = %d\n", &Point3d::z);
    Point3d p3d;
    p3d.x = p3d.y = p3d.z = 1;
    Point2d* p2d = &p3d;
    p2d->y = 2;

    return 0;
}

输出结果是:

&Point2d::y = 0
&Point3d::x = 0
&Point3d::y = 0
&Point3d::z = 8

第1、2行输出是0很正常,因为对于Point2d类来说只有一个成员y,也没有继承其他类,所以y的偏移值是0,第2行输出的是x的偏移值,它从Point类继承而来,排在最前面,所以偏移值也是0。但为什么第3行输出也是0?难道不应该是4吗?从第4行的输出看到z的偏移值是8,说明前面确实有两个成员在那里了。其实这里应该是编译器做了调整了,因为Point2d是第二基类,访问第二基类及之后的类时需要调整this指针,也就是将Point3d对象的起始地址调整为Point2d的起始地址,一般是将Point3d的地址加上前面子类的大小,如 &p3d+sizeof(Point) 。来看看上面代码生成的汇编代码:

main:                           # @main
    # 略...
    lea     rdi, [rip + .L.str]
    xor     eax, eax
    mov     esi, eax
    mov     al, 0
    call    printf@PLT
    lea     rdi, [rip + .L.str.1]
    xor     eax, eax
    mov     esi, eax
    mov     al, 0
    call    printf@PLT
    lea     rdi, [rip + .L.str.2]
    xor     eax, eax
    mov     esi, eax
    mov     al, 0
    call    printf@PLT
    lea     rdi, [rip + .L.str.3]
    mov     esi, 8
    mov     al, 0
    call    printf@PLT
    mov     dword ptr [rbp - 8], 1
    mov     dword ptr [rbp - 12], 1
    mov     dword ptr [rbp - 16], 1
    xor     eax, eax
    lea     rcx, [rbp - 16]
    cmp     rcx, 0
    mov     qword ptr [rbp - 32], rax       # 8-byte Spill
    je      .LBB0_2
    lea     rax, [rbp - 16]
    add     rax, 4
    mov     qword ptr [rbp - 32], rax       # 8-byte Spill
.LBB0_2:
    mov     rax, qword ptr [rbp - 32]       # 8-byte Reload
    mov     qword ptr [rbp - 24], rax
    mov     rax, qword ptr [rbp - 24]
    mov     dword ptr [rax], 2
    # 略...
    ret
# 略...

上面汇编代码中的第3到第7行对应的是上面C++代码的第一条printf打印语句(C++代码第17行),这里可以看到给printf函数传递了两个参数,分别通过rdi寄存器和esi寄存器,rdi寄存器保存的是第一个参数字符串,它的地址是 [rip + .L.str].L.str是字符串存储在数据段中的位置标签,rip+这个标签可以取得它的偏移地址,以下的 .L.str.1、.L.str.2.L.str.3都是字符串的位置标签),esi是第二个参数,这里的值被设为0了。

第8到12行汇编代码对应的是C++代码中的第二条printf打印语句,同样地,给rdi寄存器设置字符串的地址,给esi寄存器设置值为0。第13到第17行对应的是第三条printf打印语句,第18到第21行就是对应C++代码中的第四条printf打印语句,可以看到编译器在编译期间已经确定好了它们的偏移值为0, 0, 0, 8

第22到24行对应的C++的第22行代码,是对对象的成员进行赋值,可以看到通过对象来存取数据成员跟独立的类存取数据成员是一样的,已经知道了每个成员的内存地址了,所以存取的效率跟独立的类的存取效率没有差别。

汇编代码的第25行到37行对应C++的第23、24行代码,是将Point3d的地址转换成父类Point2d的指针类型,通过父类Point2d的指针来访问数据成员。前面提到过的将子类转换成第2及之后的基类时会进行this指针的调整,这里就是具体的实现。相当于伪代码:Point2d* p2d = &p3d+sizeof(Point),其实这里应该还需要判断下p3d是否为0,所以正确应该是:Point2d* p2d = &p3d ? &p3d+sizeof(Point) : 0。上面的第26到29行即是判断是否为0,如果为0则跳转到第33行,如果不为0则将p3d的地址 [rbp - 16] 加上44Point类的大小,然后存放在 [rbp - 32] ,再加载到rax寄存器中,然后对其赋值2(汇编代码第37行)。

通过分析汇编代码,多重继承的情况,如果是通过对象来存取数据成员,是跟独立类的存取效率是一致的,如果是通过第二及之后的基类的指针来存取,则需要调整this指针,可以看到对应的汇编代码也多了好好多行,所以效率上会有一些损失。

如果您感兴趣这方面的内容,请在微信上搜索公众号iShare爱分享并关注,以便在内容更新时直接向您推送。
image

标签:存取,printf,mov,C++,rbp,p3d,深度,Point3d,Point2d
From: https://www.cnblogs.com/isharetech/p/18150815

相关文章

  • 【笔记】动手学深度学习-前言
    1、学习深度学习,首先第一点要亲自动手。2、相关anacoda的环境的安装方法,用来隔绝相关的依赖关系,防止安装包冲突。3、机器学习程序不同于一般程序,能够随着数据的增加,通过调节内部的参数,展现出一定的智能的想象。4、机器学习中的核心组件:数据、模型、目标函数、算法。5、常用的......
  • EasyMR6.2 全面解读:四大功能深度优化,解锁全新大数据处理和计算体验
    在刚刚过去的2024春季发布会上,袋鼠云带来了数栈产品V6.2版本的全新发布。其中,EasyMR作为数栈V6.2中的一项关键能力,代表了袋鼠云对大数据生态的深入理解和持续创新。EasyMR(后文统称EMR)是袋鼠云基于Hadoop、Hive、Spark、Flink、HBase等开源组件,构建的弹性计算引擎,提供安全可靠......
  • LED车灯IC降压恒流驱动AP5103大功率95%高效率深度调光摩托车灯芯片
    产品描述AP5103是一款效率高,稳定可靠的LED灯恒流驱动控制芯片,内置高精度比较器,固定关断时间控制电路,恒流驱动电路等,特别适合大功率LED恒流驱动。AP5103采用ESOP8封装,散热片内置接SW脚,通过调节外置电流检测的电阻值来设置流过LED灯的电流,支持外加电压线性调光,最大电......
  • 掌控基础设施,加速 DevOps 之旅:IaC 深度解析
    在当今的DevOps世界中,基础设施即代码(IaC)是一个非常重要的概念。它在整个行业几乎无处不在,是现代工程角色的绝对关键。 本文将主要包含IaC的定义和它的好处,同时将Walrus作为最佳实践来进行详细讲解。 什么是基础设施即代码(IaC)用最简单的话来说,就是使用代码定义需要在......
  • C++ 上位软件通过Snap7开源库访问西门子S7-1200/S7-1500数据块的方法
    前言本人一直从事C++上位软件开发工作较多,在之前的项目中通过C++访问西门子PLCS7-200/S7-1200/S7-1500并进行数据交互的应用中一直使用的是ModbusTCP/ModbusRTU协议进行。Modbus上位开源库采用的LibModbus。经过实际应用发现Modbus开源库单次发送和接受的数据不能超......
  • C++ 连接pg数据库
    环境:windows10vs2022引入pqxxs一些增删改查的示例代码#include"pqxx/pqxx"voidinsertPg(){try{//建立连接pqxx::connectionconn("dbname=postgresuser=postgrespassword=123hostaddr=127.0.0.1port=5432");//添加数据......
  • datawhale-动手学图深度学习task04
    动手学图深度学习图表示学习研究在嵌入空间(EmbeddingSpace,指在高维数据被映射到低维空间的数学结构)表示图的方法,在图上表示学习核嵌入指的是同一件事,“嵌入”是指将网络中的每个节点映射到低维空间(需要深入了解节点的相似性和网络结构),旨在捕捉图结构中的拓扑信息、节点内容信......
  • 深度学习-nlp-NLP之实现ai写唐诗--75
    目录1.训练2.推理3.效果1.训练importcollectionsimportnumpyasnpimporttensorflowastf#-------------------------------数据预处理---------------------------#poetry_file='./data/poetry.txt'#诗集poetrys=[]withopen(poetry_file,"r",......
  • 关于c++输入输出缓冲区,和IO加速流的一些理解
    首先,让我们来介绍一下这个函数ios::sync_with_stdio();这个函数在缺省状态下默认为true,即开启,这个函数的作用是同步c和c++的缓冲区这个操作是c++为了兼容c而做出的保守决定,即将c和c++的缓冲区合并为一个,但是这样会带来性能上的开销为什么呢?因为这个兼容缓冲区先执行c的输入输......
  • 深度学习-nlp-NLP之实现skip-gram--74
    目录1.数据的获取2.数据加载3.制作数据集4.制作训练集5.模型定义6.训练模型7.可视化8.结果1.数据的获取#导入一些需要的库#由于Python是由社区推动的开源并且免费的开发语言,不受商业公司控制,因此,Python的改进往往比较激进,#不兼容的情况时有发生。Python为了确保......