《深度探索C++对象模型》读书笔记(二)
第三章:Data语意学
这一章主要讲了类中的数据在内存中是如何分配的,包括(多重)继承和多态。
让我们首先从一段代码开始:
class X{};
class Y :virtual public X{};
class Z :virtual public X{};
class A :public Y, public Z{};
std::cout << sizeof(X) << std::endl;
std::cout << sizeof(Y) << std::endl;
std::cout << sizeof(Z) << std::endl;
std::cout << sizeof(A) << std::endl;
在VS2013上输出的结果为1,4,4,8。为什么会这样呢?这就涉及到编译器针对C++语法而采取的对象模型。X虽然是一个空类,但为了使它的对象具有地址,编译器实际上是会为它的对象分配1个字节大小的空间,因此它的大小为1。Y和Z是对X的虚拟继承,编译器会为它们各分配一个指针,指向X的对象,因此它们的大小为4.A同理,它是Y和Z的多重继承,因此编译器会为它分配两个指针,所以它的大小为8.内存分布如图所示:

书中还提到了一种模型,其输出的结果为1,8,8,12。这种模型的思路是给每一个空类分配1 byte的空间,再考虑到4 byte的alignment,因此可以推导出书中的结果,在这里不再赘述。
关于类中Member的布局,C++ Standard是这样规定的:static data members不会被放到对象的布局之中;在同一个access section(public、private、protected等区段),member的排列是按照声明顺序分布的,但不一定是连续排列(因为存在alignment);同时,对于不同的access section,它们的data members自由排列,不必在乎声明顺序(也就是说access section之内是按照声明顺序排列,而access section之间自由排列)。
之后,书中讨论了单一继承、多态、多重继承、虚拟继承下类中内存分布的表现,这也是本章的重中之重。
这是一个很简单的程序:
Point3d origin;
origin.x=0.0;
执行这段程序所需要的时间和空间代价,随x的性质而不同。让我们分情况讨论:
1.x是static data members
类的每个static成员都只有一个实例,存放在程序的data segment之中,和对象无关;因此对于这种情况,对x的存取并不会招致任何空间和时间上的额外负担。
2.x是 nonstatic data members
访问类的nonstatic data members时,实际上编译器做了如下工作:
//源代码
origin.x=0.0;
//编译后的代码
*(&origin+(&Point3d::x))=0.0;
也就是说,为了得到x的地址,编译器需要将origin的起始地址加上x的地址偏移量。(实际上,起码在VS里用&Point3d::x来表示偏移量是有问题的,但思想可以先这样理解)
3.x是基类的变量,没有多态与多重继承
在有继承的情况下,可能会导致空间上的浪费。我们来看这样一个例子:

这个类中存有一个int和三个char,如果我们把这些变量都放到一个类中声明,那么算上alignment,它的对象大小为8字节。
假设我们要继承:

那么Concrete3的对象的大小将达到16字节,比原先的设计多了100%!
这是因为alignment导致的,因为C++的对象模型中,在一个继承而来的类的内存分布里,各个基类需要分别遵循alignment,从而导致了空间的浪费。具体地对象布局可见下图:

4.加上多态
在这种情况下,无论是时间还是空间上,访问类的成员都会带来一定额外的负担,主要体现在以下几个方面:
1.virtual table,用来存放它所声明的每一个virtual functions的地址。
2.每一个对象中会有一个vptr,提供执行期的链接。
3.编译器会重写constructor和destructor,使其能够创建和删除vptr。
5.多重继承
在多重继承的条件下,对于指针之间的赋值需要运行时计算。举个例子,以下的继承结构:

我们声明几个对象和指针并赋值:
Vertex3d v3d;
Vertex *pv;
Point2d *p2d;
Point3d *p3d;
pv=&v3d;
p2d=&v3d;'
p3d-&v3d;
对于p3d和p2d的赋值,只需要直接将v3d的地址赋过去就好。但对于pv的赋值,编译器需要计算一个Vertex在Vertex3d中的偏移量,从这个偏移量起始来得到pv的地址。因为类之间的内存分布如下所示:

Vertex3d中Vertex部分的起始地址并不是Vertex3d对象的起始地址,因此对pv赋值需要一个运行时计算的开销。
6.虚拟继承
在虚拟继承中,C++对象模型将Class分为两个区域,一个是不变区域,直接存储在对象中;一个是共享区域,存储的是virtual base class subobjects,它在内存中单独存储在某处,derived class object持有指向它的指针。在cfront编译器中,每一个derived class object中安插一些指针,每个指针指向一个virtual base class,为此需要付出相应的时间和空间成本。如下所示:
//具体的类同上一节多重继承,不同的是Vertex和Point3d虚拟继承了Point2d
void Point3d::operator+=(const Point3d&rhs)
{
x+=rhs.x;
y+=rhs.y;
z+=rhs.z;
} //编译器翻译后的版本
_vbcPoint2d->x+=rhs._vbcPoint2d->x;
_vbcPoint2d->y+=rhs._vbcPoint2d->y;
z+=rhs.z;
这只是最基本的解决方案,书中还提出了一些编译器优化时间和空间的方法,感兴趣可以深入阅读一下。
最后,书中探讨了如何获得类中某个成员的地址偏移。我在这里就总结两种方法:
1.((int)&((structure*)0)->member)
2.先通过 int Test::* pOffset = &Test::x;获取偏移变量,再利用reinterpret_cast<int>(*(void**)(&pOffset))将其转化为整形量。
《深度探索C++对象模型》读书笔记(二)的更多相关文章
- 《CSS世界》笔记二:盒模型四大家族
上一篇:<CSS世界>笔记一:流/元素/尺寸下一篇:<CSS世界>笔记三:内联元素与对齐 写在前面 在读<CSS世界>第四章之前,粗浅的认为盒模型无非是margin ...
- CSS揭秘读书笔记 (一)
CSS揭秘读书笔记 (一) 一.半透明边框 要想实现半透明边框可以使用border: border: 10px solid hsla(0,0%,100%,.5); background: ...
- 《你必须知道的.NET》读书笔记二:小OO有大原则
此篇已收录至<你必须知道的.Net>读书笔记目录贴,点击访问该目录可以获取更多内容. 一.单一职责原则 (1)核心思想:一个类最好只做一件事,只有一个引起它变化的原因 (2)常用模式:Fa ...
- spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定
本文是王福强所著<<spring揭秘>>一书的读书笔记 我们前面就说过,Spring的IoC容器时一个IoC Service Provider,而且IoC Service Pr ...
- ES6读书笔记(二)
前言 前段时间整理了ES6的读书笔记:<ES6读书笔记(一)>,现在为第二篇,本篇内容包括: 一.数组扩展 二.对象扩展 三.函数扩展 四.Set和Map数据结构 五.Reflect 本文 ...
- 《精通CSS》读书笔记(一)
最近新添16本书,目前开始看陈剑瓯翻译的<精通CSS——高级Web标准解决方案>(Andy Budd, CSS Mastery -- Advanced Web Standards Solu ...
- spring揭秘 读书笔记 二 BeanFactory的对象注冊与依赖绑定
本文是王福强所著<<spring揭秘>>一书的读书笔记 我们前面就说过,Spring的IoC容器时一个IoC Service Provider,并且IoC Service Pr ...
- 【记】《.net之美》之读书笔记(二) C#中的泛型
前言 上一篇读书笔记,很多小伙伴说这本书很不错,所以趁着国庆假期,继续我的读书之旅,来跟随书中作者一起温习并掌握第二章的内容吧. 一.理解泛型 1.为什么要使用泛型?-----通过使用泛型,可以极大地 ...
- Mastering Web Application Development with AngularJS 读书笔记(二)
第一章笔记 (二) 一.scopes的层级和事件系统(the eventing system) 在层级中管理的scopes可以被用做事件总线.AngularJS 允许我们去传播已经命名的事件用一种有效 ...
- how tomcat works 读书笔记(二)----------一个简单的servlet容器
app1 (建议读者在看本章之前,先看how tomcat works 读书笔记(一)----------一个简单的web服务器 http://blog.csdn.net/dlf123321/arti ...
随机推荐
- SQA
SQA 一.SQA过程 首先组成一个团队,遵循敏捷开发的原则,进行分工合作,为软件开发编造一个用例故事,画出相应的图,小组讨论合作后写代码,软件质量保证(SQA-Software Quality As ...
- input 和 button元素 作为提交、重置、按钮功用的区别。
首先,input元素和button元素 都是可以作为普通按钮.提交按钮.重置按钮的. <input type="button" value="button" ...
- 20165228《网络对抗技术》Exp0 Kali安装 Week1
2018-2019-2 <网络对抗技术>Exp0 Kali安装 Week1 Kali下载:来自官网 Kali安装 参考自kali安装教程 (因为写博客时已经安装好kali,所以这部分没有自 ...
- 2018上C语言程序设计(高级)- 第1次作业成绩
作业地址 https://edu.cnblogs.com/campus/hljkj/CS2017-01/homework/1638 评分准则 第一次作业各项成绩包括三项: 完成PTA所有题目:20分 ...
- storm入门基础实例(无可靠性保证实例)
本实例为入门篇无可靠性保证实例,关于storm的介绍,以及一些术语名词等,可以参考Storm介绍(一).Storm介绍(二). 本案例是基于storm0.9.3版本 1.案例结构 案例:Word Co ...
- 固态硬盘Ghost安装Windows 10无法引导的问题
机器配置如下: 电脑型号 技嘉 B360M POWER 台式电脑 操作系统 Windows 10 64位 ( DirectX 12 ) 处理器 英特尔 Core i7-8700 @ 3.20GHz 六 ...
- 百战程序员——EL、JSTL
1.EL表达式可以操作作用域中的属性,也可以操作普通的局部变量.对吗? 不对 El表达式一般支持作用域(application.session.request.pagecontext)中的属性.EL变 ...
- Android自定义标签列表控件LabelsView解析
版权声明:本文为博主原创文章,未经博主允许不得转载. 无论是在移动端的App,还是在前端的网页,我们经常会看到下面这种标签的列表效果: 标签从左到右摆放,一行显示不下时自动换行.这样的效果用And ...
- for和for in区别
for ... in 循环中的代买每执行一次,就会对数组的元素或者对象的属性进行一次循环操作. eg:应该用在非数组对象的遍历上,使用for-in进行循环也被称为“枚举”. for (变量 in 对象 ...
- htop的安装和使用
1.Ubuntu16.04中htop安装很简单 sudo apt-get install htop 2.htop的使用,直接以普通用户输入htop命令即可执行 关于其基本介绍和使用方式在见下 http ...