C++对象模型——Data 语意学(第三章)

第3章    Data 语意学

计算如下代码的sizeof结果:

class X{};
class Y : public virtual X{};
class Z : public virtual X{};
class A : public Y, public Z{};

上述X,Y,Z,A中没有任何一个 class 内含明显的数据,只表示了继承关系,所以认为每一个 class 的大小都是0.这样想法是错误的.即使 class X的大小也不为0.

一个空的 class 如:

// sizeof(X) = 1
class X{};

事实上它并不是空的,它有一个隐式的1 byte,那是编译器插入的一个char,这使得这个 class 的Object可以在内存中分配唯一的地址:

X a, b;
if (&a == &b)
    cerr << "yipes!" << endl;

Y和Z的大小都是4(在g++4.8上测试),这个大小和机器有关,也和编译器有关.事实上Y和Z的大小受到三个因素的影响:

 1.    语言本身所造成的额外负担(overhead)    当语言支持 virtual base classes时,就会导致一些额外负担.在 derived class 中,这个额外负担反映在某种形式的指针上,它或者指向 virtual base class subobject,或者指向一个相关表格;表格中存放的若不是 virtual base class subobject的地址,就是其偏移量(offset).

 2.    编译器对于特殊情况所提供的优化处理    virtual base class X subobject的1byte也出现在 class Y和 class Z上,传统上它被放在derived class 的固定部分的尾端.某些编译器会对empty virtual base class 提供特殊支持.

 3.    Alignment限制    在大部分机器上,群聚的结构体大小会受到alignment的限制,使它们能够更有效率地在内存中被存取.alignment将数值调整到4或者8的整数倍.

Empty virtual class已经成为C++ OO设计的一个特有术语,它提供一个 virtual interface,没有定义任何数据,某些新的编译器(例如g++4.8,因为Lippmanz这本书是2000年写的)对此提供了特殊处理.在这个策略下,一个empty virtual base class 被视为derived
class object最开头的部分,也就是说它没有花费任何的额外空间,这就节省了上述第二点的1bytes(因为既然有了members,就不需要原本为了empty class 而插入的一个char),也就不再需要第三点所说的3bytes的填补.只剩下第一点所说的额外负担,在此模型下Y和Z的大小都是4而不是8(早期的编译器为8).

编译器之间的潜在差异说明了C++对象模型的演化,这个模型为一般情况提供解决之道,当特殊情况逐渐被挖掘出来时,种种尝试(尝试错误)于是被引入,提供优化的处理.如果成功,启发的方法就提升为普遍的策略,并跨越各种编译器而合并.它被视为标准,久而久之就成了语言的一部分. virtual function table就是一个例子.另一个例子是第二章介绍的"named return value(NRV)优化"

那么 class A的大小呢?很明显,某种程度上要视编译器而定.一个 virtual class subobject只会在derived class 中存在一份实体,不管它在 class 继承体系中出现了多少次! class A的大小由下列几点决定:

1.    被共享的唯一一个 class X实体,大小为1bytes

2.    base class Y的大小,减去"因virtual base class X而配置"的大小,结果是4bytes.Base class Z也是4bytes,加起来是8bytes.

3.    class A的alignment数量(如果有的话).前述三项总合,表示调整前的大小是9bytes.class A必须调整至4bytes边界.所以需要填补3bytes,结果是12bytes.

如果考虑"特别对empty virtual base class做了处理"的编译器,base class Y的大小为4bytes, base class Z的大小为4bytes,因此 class A的大小为8bytes.如果在 virtual base class X中存放一个(或以上)的data members,两种编译器("有特殊处理"和"没有特殊处理"的)就会产生出完全相同的对象布局.

当 class X中没有数据,empty base class 不花费额外的空间,对 class X,Y,Z,A求sizeof的结果如下所示:

当 class X中存放一个数据char c;时,结果如下所示(两种编译器结果相同):

一个 class 的data members,一般而言可以表现这个 class 在程序执行时的某种状态.Nonstatic data members放置的是"个别的class object"感兴趣的数据, static data members则放置的是"整个class"感兴趣的数据.

C++对象模型尽量以空间优化和存取速度优化的考虑来表现nonstatic data members,并且保持和C语言 struct 数据配置的兼容性.它把数据直接存放在每一个 class object之中.对于继承而来的nonstatic data members(不管是 virtual 或nonvirtual base class )也是如此.不过并没有强制定义它们的拍了顺序,至于 static data members,则被放置在程序的一个global data segment中,不会影响个别的
class object的大小
.在程序中,不管该 class 被产生出多少个objects(经由直接产生或间接派生), static data members永远只存在一份实体(甚至即使该 class 没有任何object实体,其 static data members也存在).但是一个 template class 的 static data members的行为稍微不同.

每一个 class object因此必须有足够的大小以容纳它所有的nonstatic data members.有时候可能令人吃惊,因为它可能比想象的还大,原因是:

1.    有编译器自动加上的额外data members,用以支持某些语言特性(主要是各种virtual特性)

2.    因为alignment(边界调整)的需要

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-04 04:37:23

C++对象模型——Data 语意学(第三章)的相关文章

C++对象模型——&quot;继承&quot;与Data Member(第三章) .

3.4 "继承"与Data Member 在C++继承模型中,一个derived class object所表现出来的东西,是其自己的members加上其base class members的总和.至于derived class members和base class members的排列次序并未在C++ Standard中强制指定:理论上编译器可以自由安排.在大部分编译器上,base class members总是先出现,但属于 virtual base class的除外. 了解这种继

深度探索C++对象模型 第三章 Data 语意学

一个有趣的问题:下列 类 sizeof大小 class X{}    //1 class Y:public virtual X{} //4 or 8 class Z:public virtual X{} // 4 or 8 class A:public Y,public Z{} // 8 or 12 主要原因:为了保持每一个类生成对象在内存中的唯一性,编译器必须要给空类生成一个char来维持object的唯一性: 而virtual继承中,仅保持了base class的指针,有些编译器会继承bas

《深度探索c++对象模型》chapter3 Data语意学

一个空的class:如 class X{} ; sizeof(X)==1; sizeof为什么为1,他有一个隐晦的1 byte,那是被编译器安插进去的一个char,这使得class2的两个objects得以在内存中配置独一无二的地址: X a,b; if(&a==&b) cerr<<"yipes!"<<endl; class X{}; class Y:public virtual X{}; class Z:public virtual X{};

Data语意学里的一些问题

今天和小伙伴讨论了第三章data语意学的指一些的知识,感觉很有必要总结一下,似乎不总结知识就会溜走,所以冒夜写一下吧. 首先看这样的一个继承的例子: class X{ }; class Y: public virtualX { }; class Z: public virtualX { }; class A: public Y,publicZ { }; 分别对X Y Z A取sizeof,结果可能是1, 8, 8, 12.当然我的机器是1, 4, 4, 8.一般来说老的机器会是第一种结果,因为编

第三章 Linux操作系统的安装

第三章 Linux操作系统的安装 因为笔者一直都是使用CentOS,所以这次安装系统也是基于CentOS的安装.把光盘插入光驱,设置bios光驱启动.进入光盘的欢迎界面. 其中有两个选项,可以直接按回车,也可以在当前界面下输入 linux text 按回车.前者是图形下安装,可以动鼠标的,后者是纯文字形式的.建议初学者用前者安装.直接回车后,出现一下界面: 这一步是要提示你是否要校验光盘,目的是看看光盘中的安装包是否完整或者是否被人改动过,一般情况下,如果是正规的光盘不需要做这一步操作,因为太费

《操作系统精髓与设计原理》习题第三章

第三章习题 3.10.1关键术语 阻塞态:进程在某些事件发生之前不能执行,等待这种事件发生的状态. 退出态:操作系统从可执行进程组中释放出的进程,自身停止了,或者因某种原因被取消. 内核态:某些指令只能在特权状态下执行,而这种特权状态称为内核态. 子进程:由一个进程创建的进程,该进程的终止受父进程的影响. 中断:由外部事件引发进程挂起,CPU转而去处理发起中断的事件,并处理结束后恢复进程的执行. 模式切换:CPU由用户态和核心态之间相互切换. 新建态:进程创建时仅仅创建了对应的进程控制块而没有在

HBase in Action前三章笔记

近期接触HBase,看了HBase In Action的英文版.開始认为还行,做了些笔记.可是兴许看下去,越来越感觉到实战这本书比較偏使用上的细节,对于HBase的具体设计涉及得很少.把前三章的一些笔记帖一下.后面几章内容不打算整理了.并非说书内容不好. key-value存储.强一致性,多个RegionServer节点对client端是不暴露细节的 使用场景:典型的web-search, capture incremental data, ad. click stream, content s

KnockoutJS 3.X API 第三章 计算监控属性(5) 参考手册

计算监控属性构造参考 计算监控属性可使用以下形式进行构造: ko.computed( evaluator [, targetObject, options] ) - 这种形式是创建一个计算监控属性最常见的情况. evaluator - 用于返回计算值的函数. targetObject-如果给出定义的值this时KO调用回调函数.参见部分第三章 计算监控属性(1) 使用计算监控属性以获取更多信息. options - 计算监控属性的其他属性的对象.请参见下面的完整列表. ko.computed(

第三章 rsync通过远程ssh实现数据备份

第三章 rsync通过远程ssh实现数据备份 通过远程shell访问,实现数据同步备份 拉--即将远程服务器上的数据同步到本地服务器上. 推--即将本地服务器上的数据同步到远程服务器上. 语法格式: 拉操作==>     rsync   选项    用户名@备份源服务器IP地址:备份源目录    目标目录 推操作==>     rsync   选项    备份源目录    用户名@目标服务器IP地址:目标目录 环境: 本地服务器为:DataServer.  IP地址为:192.168.88.8