深度解读《深度探索C++对象模型》之数据成员的存取效率分析(一)
接下来我将持续更新“深度解读《深度探索C++对象模型》”系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文和全部的文章列表。
在《深度解读《深度探索C++对象模型》之C++对象的内存布局》这篇文章中已经详细分析过C++的对象在经过封装后,在各种情况下的内存布局以及增加的成本。本文将进一步分析C++对象在封装后,数据成员的存取的实现手段及访问的效率。在这里先抛出一个问题,然后带着问题来一步一步分析,如下面的代码:
class Point {};
Point p;
Point *pp = &p;
p.x = 0;
pp->x = 0;
上面的代码中,对数据成员x的存取成本是什么?通过对象p来存取成员x和通过对象的指针pp来存取成员x的效率存在差异吗?要搞清楚这个问题,得看具体的Point类的定义以及成员x的声明方式。Point类可能是一个独立的类(也就是没有从其他类继承而来),也可能是一个单一继承或者多重继承而来的类,甚至也有可能它的继承父类中有一个是虚拟基类(virtual base class),成员x的声明可能是静态的或者是非静态的。下面的几节将根据不同的情况来一一分析。
类对象的数据成员的存取效率分析系列篇幅比较长,所以根据不同的类的定义划分为几种情形来分析,这篇先来分析静态数据成员的情况。
静态数据成员在编译器里的实现
在前面的文章中说过,类中的静态数据成员是跟类相关的,而非跟具体的对象有关,它存储在对象之外,具体的存储位置是在程序中的数据段中。它其实跟一个全局变量没什么区别,在编译期间编译器就已经确定好了它的存储位置,所以能够确定它的地址。看一下下面的代码:
#include <cstdio>
int global_val = 1;
class Base {
public:
int b1;
static int s1;
};
int Base::s1 = 1;
int main() {
static int static_var = 1;
int local_var = 1;
Base b;
printf("&global_val = %p\n", &global_val);
printf("&static_var = %p\n", &static_var);
printf("&local_var = %p\n", &local_var);
printf("&b.b1 = %p\n", &b.b1);
printf("&b.s1 = %p\n", &b.s1);
return 0;
}
程序输出的结果:
&global_val = 0x102d74000
&static_var = 0x102d74008
&local_var = 0x16d0933f8
&b.b1 = 0x16d0933f4
&b.s1 = 0x102d74004
可以看到全局变量global_val和局部静态变量static_var以及类中的静态数据成员s1的地址是顺序且紧密排列在一起的,而且跟其他的两个局部变量的地址相差较大,说明这几个都是一起存储在程序的数据段中的。类中的非静态数据成员b1跟局部变量local_var一样,是存放在栈中的。
可以进一步看看生成的汇编代码,看一下是怎么存取静态数据成员的,下面节选部分的汇编代码:
main: # @main
# 略...
lea rdi, [rip + .L.str]
lea rsi, [rip + global_val]
mov al, 0
call printf@PLT
lea rdi, [rip + .L.str.1]
lea rsi, [rip + main::static_var]
mov al, 0
call printf@PLT
# 略...
lea rdi, [rip + .L.str.4]
lea rsi, [rip + Base::s1]
mov al, 0
call printf@PLT
# 略...
ret
global_val:
.long 1 # 0x1
Base::s1:
.long 1 # 0x1
main::static_var:
.long 1 # 0x1
从汇编代码中看到,global_val、Base::s1和main::static_var是定义在数据段中的,在代码中直接使用它们的地址,如:
lea rsi, [rip + Base::s1]
则是将Base::s1的地址加载到rsi寄存器中,作为参数传递给printf函数。这也证明了它跟全局变量,普通的静态变量是没有区别的。结论就是,类中的静态数据成员的存取方式是直接通过一个具体的地址来访问的,跟全局变量毫无区别,所以效率上也跟访问一个全局变量一样。
通过不同方式存取静态数据成员的效率差异
访问类的静态数据成员可以通过类名来访问,如Base::s1,也可以通过对象来访问,如b.s1,甚至是通过指针来访问,如pb->s1。那么这几种访问方式有什么差别?或者说是否有效率上的损失?其实这几种访问方式本质上没有任何差别,编译器会转换成如Base::s1一样的方式,后面的两种方式只是语法上的方便而已,看一下汇编代码就一目了然。把上面的例子多余的代码删除掉,只留下Base类,然后main函数中增加几行打印,如下:
Base b;
Base *pb = &b;
printf("&Base::s1 = %p\n", &Base::s1);
printf("&b.s1 = %p\n", &b.s1);
printf("&pb->s1 = %p\n", &pb->s1);
输出的结果当然是同一个地址了,下面是节选的汇编代码:
lea rdi, [rip + .L.str]
lea rsi, [rip + Base::s1]
mov al, 0
call printf@PLT
lea rdi, [rip + .L.str.1]
lea rsi, [rip + Base::s1]
mov al, 0
call printf@PLT
lea rdi, [rip + .L.str.2]
lea rsi, [rip + Base::s1]
mov al, 0
call printf@PLT
可以看到C++中的几行不同的访问方式在汇编代码中都转换为同样的代码:
lea rsi, [rip + Base::s1]
继承而来的静态数据成员的存取分析
我们已经知道类中的静态数据成员是跟对象无关的,所有的对象都共享同一个静态数据成员。但是如果继承而来的静态数据成员又是怎样的呢?假如定义一个Derived类,它是Base类的派生类,那么静态数据成员s1的情况又是如何?其实无论继承多少次,静态数据成员都只有一份,无论是Derived类还是Base类,它们都共享同一个静态数据成员s1,可以通过下面的例子来验证一下:
#include <cstdio>
class Base {
public:
int b1;
static int s1;
};
int Base::s1 = 1;
class Derived: public Base {};
int main() {
Derived d;
printf("&d.s1 = %p\n", &d.s1);
printf("d.s1 = %d\n", d.s1);
d.s1 = 2;
Base b;
printf("&b.s1 = %p\n", &b.s1);
printf("b.s1 = %d\n", b.s1);
return 0;
}
程序输出的结果:
&d.s1 = 0x10028c000
d.s1 = 1
&b.s1 = 0x10028c000
b.s1 = 2
可以看到通过Derived类的对象d和Base类的对象b访问到的都是同一个地址,通过对象d修改s1后,通过对象b可以看到修改后的值。
如果您感兴趣这方面的内容,请在微信上搜索公众号iShare爱分享并关注,以便在内容更新时直接向您推送。
深度解读《深度探索C++对象模型》之数据成员的存取效率分析(一)的更多相关文章
- 深度探索C++对象模型
深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...
- 深度探索C++对象模型之第四章:函数语义学
C++有三种类型的成员函数:1.static/nonstatic/virtual 一.成员的各种调用方式 C with Classes 只支持非静态成员函数(Nonstatic Member Func ...
- 深入探索C++对象模型-1
概述 在实际生产中,遇到一个复杂的类,如果能看出这个类的内存模型结构,那么以后的操作基本就没有难度的: 所以说,学会分析一个类的内存模型,是每一个C++程序员必须要会的知识. 下面,就让我们来了解C+ ...
- 深度探索C++对象模型之第三章:数据语义学
如下三个类: class X { }: class Y :public virtual X { }; class Z : public virtual X {}; class A :public Y, ...
- [读书系列] 深度探索C++对象模型 初读
2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...
- 拾遗与填坑《深度探索C++对象模型》3.3节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- 拾遗与填坑《深度探索C++对象模型》3.2节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- 《深度探索C++对象模型》读书笔记(一)
前言 今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++.操作系统.计算机网络.算法和数据结构等.C++就先从这本<深度探索C++对象模型>开始.不同于& ...
- SQL Server 2019 深度解读:微软数据平台的野望
本文为笔者在InfoQ首发的原创文章,主要利用周末时间陆续写成,也算近期用心之作.现转载回自己的公众号,请大家多多指教. 11 月 4 日,微软正式发布了其新一代数据库产品 SQL Server 20 ...
- 读书笔记《深度探索c++对象模型》 概述
<深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...
随机推荐
- 音标 重音 u 用 中文 吁 去记忆 骑马让马停下来的 吁 - 英语
音标 重音 u 用 中文 吁 去记忆 骑马让马停下来的 吁
- idea dev 分支合并到 master 流程
合并分支前要全部提交 包括config.js 要不merge时候很麻烦 1 切换到master分支 Checkout 2 Merge into Current 3 commit push 4 切换回d ...
- pfx文件导出pem和私钥,更换网站域名证书
openssl 路径: C:\Program Files\OpenSSL-Win64\bin -- 导出pem证书openssl pkcs12 -in C:\BackUp\Lightning\cert ...
- TornadoFx中的css美化
原文地址:TornadoFx中的css美化 - Stars-One的杂货小窝 TornadoFx中使用类重新对css进行了封装,所以可以用代码的形式来书写样式 说明 除了Text,其他的若是要修改文字 ...
- 云VR给当今的教育行业带来哪些契机
教育一直是国之根本大策,培养下一代高科技新型人才是新时代规划的重要建设目标.教育的授课方式也别出心裁,不断地涌现出教育的新模式.3DCAT 云VR别出心裁,为教育行业带来新的教学方式和简化管理的全新系 ...
- 三维模型3DTile格式轻量化的数据压缩与性能平衡关系分析
三维模型3DTile格式轻量化的数据压缩与性能平衡关系分析 对于三维模型的3DTile格式轻量化处理,数据压缩和性能之间的平衡关系是一个重要的考虑因素.以下是这两者关系的详细分析: 1.数据压缩与加载 ...
- 优化您的部署:Docker 镜像最佳实践
介绍 在快速发展的软件开发和部署领域,Docker 已成为容器化的强大工具,为打包.分发和运行应用程序提供了一种标准化的高效方式.Docker 镜像在这一过程中发挥着至关重要的作用,是容器化应用程序的 ...
- Button按钮Effect的用法
教大家写一个好看的Button按钮 代码简单粗暴 <Grid > <Border Width="200" Height="30" Margin ...
- R语言数据质量分析
数据质量分析是数据预处理的前提,也是数据分析结论有效性和准确性的基础. 数据质量分析的主要任务是检查原始数据中是否存在脏数据. 脏数据一般包括: 缺失值分析 缺失值产生的原因.影响 原因: 部分信息难 ...
- FineReport 自定义工具栏样式
虽然FR界面的工具栏已经很商业化,很好看了,但是总会有那么些需求希望你可以修改工具栏的样式. 修改工具栏样式的主要思路是: 通过JQ选择器选中需要调整的元素,然后修改他们的样式 接下来,我们尝试着对工 ...