data语意学

引例:

class X{};

class Y:public virtual X{};

class Z:public virtual X{};

class A:public Y,public Z{};

X Y Z A类对象的大小是多少??

1> 没有提供empty virtual base特殊支持的编译器:1 8 8 12

2> 提供了empty
virtual base特殊支持的编译器:1 4 4 8

一个class的data
members,一般而言,可以表现这个class在程序执行时的某种状态。

nonstatic data members放置的是“个别的class object”感兴趣的数据。 C++对象模型把nonstatic
data members直接放置在每一个class object之中,对于继承而来的nonstatic
data members也是如此。

static
data members则放置的是“整个class”感兴趣的数据。C++对象模型则把static data members,则放置在程序的一个global
data segment中,不会影响个别class object的大小。

每一个class
object因此必须有足够的大小容纳它所有的nonstatic data
members。但有时候,它可能比想象的还大,原因是:

1> 由编译器自动加上的额外data
members,用以支持某种语言特性(主要是各种virtual特性-虚函数、虚基类?)

2>
因为alignment(字节对其调整)

一 Data
member的存取


static data
member

1 static data member被编译器提出class之外,视为一个global变量(但只在class声明范围之内可见)。

2 若取一个static data member的地址,会得到一个指向其数据类型的指针,而不是一个指向其class
member的指针。

如:&Point3d::chunkSize;   获得的内存地址是 int
*

3 每一个static data member只有一个实体,存放在程序的data
segment之中,每次程序取用static member,就会被内部转化为对唯一的extern实体的直接取用操作。如:

//origin.chunkSize=250;

Point3d::chunkSize=250;

//pt->chunkSize=250;

Point3d::chunkSize=250;

注:这(静态数据成员)是c++语言中"通过一个指针和通过一个对象来存取member,结论完全相同"的唯一一种情况。

4 如果多个class,都声明了一个相同的static data member,那么当他们被放在程序的data segment时,就会导致命名冲突。编译会暗中对每一个static data
member编码(即:name-mangling)以获得一个独一无二的程序识别码。

nonstatic data
members

1 nonstatic
data members直接存放在每一个class
object之中,除非经由明确(explicit)或隐式的(implicit)class
object,没有办法直接存取他们。

2
只要程序员在一个member function中直接处理一个nonstatic data member,所谓“implicit class
object”(即:this指针)就会发生。

3
欲对一个nonstatic data member进行存取操作,编译器需要把class object的起始地址加上data
member的偏移量。每一个nonstatic data member的偏移量offset在编译时期即可获得。(因此,存取一个nonstatic
data member的效率和存取一个C struct member或一个nonderived class的member是一样的)
4
取一个nonstatic data member的地址,将会得到它在class中的偏移量(offset);取一个绑定于真正class
object身上的data member的地址,将会得到该member在内存中的真正地址。

二 “继承”与data
member

在c++继承模型中,一个derived class
object所表现出来的东西,是其自己的members加上其base calss
members的总和。在大部分编译器中,base class members总是先出现,但属于virtual base
class的除外。

derived class
object中数据成员的布局与继承、virtual function、virtual base等情况有关。下面分几种情况进行讨论:

1
无继承、无virtual function

这种情况和C
struct完全一样。如下图:

     
 

2
只要继承不要多态(单一继承、不含virtual function)

一般而言,具体继承并不会增加空间或存取时间上的额外负担。

这样设计的好处是可以把管理x和y坐标的程序代码局部化。此外这个设计可以明显表现出两个抽象类之间的紧密关系。

但这样设计,容易犯两类错误:

1>
经验不足的人可能会重复设计一些相同的函数。

2> 把一个class分解成两层或更多层,有可能会为了“表现class体系之抽象化”而膨胀所需要空间。c++语言保证“出现在derived class 中的base class
subobject有其完整原样性”。举一个例子:

具体类      
                     
                  分裂为三层结构

为了保证"出现在derived
class 中的base class subobject有其完整原样性",三层结构可能的布局如图所示:

然而,如果C++语言把derived class
members(也就是concrete2::bit2或concrete3::bit3)和concrete1
subobject捆绑在一起,去掉填补空间,则在如下图语义中,base class
subobject的完整原样性就无法保证(但是gcc却采用的正在用方式):

3
单一继承加多态(即:含有虚函数)

如果我们要处理一个坐标点,而不打算在乎它是一个point2d或point3d实例(也就是我们企图以多态的方式处理2d或3d坐标点),那么我们要在继承关系中提供一个virtual
function接口。为了支持这样的特性,势必会给我们的Point2d class带来空间和存取时间上的额外负担:

1>
导入一个和Point2d有关的virtual
table,用来存放它所声明的每一个virtual functions的地址。

2>
在每一个class object中导入一个vptr,提供执行期链接,是每一个object能找到相应的virtual
table。

3>
加强版constructor,使它能够为vptr设定初值,让它指向class所对应的virtual table。

4>
加强版destructor,使它能够抹消"指向class之相关virtual table"的vptr。

加上多态后,对于每一个对象在空间上的负担就是多了一个vptr指针的空间(通常是一个word,4byte)。然而这个vptr放在类对象的什么位置最好?有两种主流设计:一种放在前端;一种放在尾端。

cfront编译器放在class object的尾端(好处:可以保证base class c struct的对象布局):

到了c++2.0后,某些编译器(gcc 和 vc6.0都是这样的)开始把vptr放到class
object的前端。(好处是与class vptr之间的offset不需要专门准备;缺点是丧失了c语言的兼容性)。

单一继承加多态后的对象布局(vptr放在尾端的情况):

4
多重继承

单一继承提供了一种自然多态。base
class和derived class的objects都是从相同的地址开始的,其差异只在于derived
object(可能)比较大。把一个derived class
object指定给base class(不管继承深度有多深)的指针或引用,该操作并不需要编译器去调停或修改地址。它很自然的可以发生,而且提供了最佳执行效率。

多重继承既不像单一继承,也不容塑造出其模型:

1> 多重继承的复杂度在于derived
class和其第二个或后继base class之间的非自然关系。

2> 多重继承的问题主要在与derived class和其第二个或后继base
class objects之间的转换:

① 对一个多重派生对象,将其地址指定给“最左端base class的指针”,情况和单一继承时相同。(二者指向相同的地址,只需地址指定操作而已)。

② 至于第二个或后继的base
class的地址指定操作,则需要将地址修改,加上(或减去)介于中间的base
class subobjects大小。

实例:

Vertex3d v3d;

Vertex *pv;

Point2d *p2d;

Point3d *p3d;

1> pv=&v3d;//被转化为:

pv=(Vertex *)(((char*)&v3d)+sizeof(Point3d));

2> 而下面的指定操作:

p2d=&v3d;

p3d=&v3d;

都只需要简单拷贝其地址就行了。

3> c++标准并没有要求Vertex3d中base class
Point3d和Vertex有特定的排列次序。但现在大多数编译器还是想cfront那样根据声明次序排列他们。

4> members的位置在编译时就固定了,所以存取第二个(或后继)base class中的一个data
member不需要额外的成本,只是一个简单的offset运算,就像单一继承一样简单-不管是经由一个指针、一个reference或是一个object来存取。

5
虚拟继承

多重继承的一个语意上的副作用就是,它必须支持某种形式的“shared
subobject继承”。在语言层面的解决办法是导入所谓的虚拟继承。

一般的实现方法如下所述:class如果内含一个或多个virtual base class
subobjects,像istream那样,将被分割为两部分:一个不变局部和一个共享局部。不变局部中的数据,不管如何衍变,总是拥有固定的offset,所以这部分可以直接存取;至于共享局部,所表现的就是virtual base
class subobject,这一部分的数据,其位置会因为每次的派生操作而有变化。所以它们只可以间接存取。

一般的布局策略是先安排好derived
class的不变部分,然后再建立其共享部分。然而如何存取class的共享不放呢?

cfront编译器会在每一个derived
class object中安插一些指针,每个指针指向一个virtual base
class。要存取继承得来的virtual base class members,可以使用相关指针间接完成。然而还存在如下优化:

1> 理想上我们希望class
object有固定的负担。所以每一个对象不应该针对每一个virtual base
class背负一个额外的指针(这样负担会导致随虚基类的数目变化而变化)。解决方案:

①Microsoft编译器:引入所谓的virtual base class
stable。每一个class object如果有一个或多个virtual base classes就会有编译器安插一个指针,指向virtual base class
stable。至于真正的virtual
base class指针被放置在virtual base class stable中。

②在virtual function
table中放置virtual base
class的offset(而不是地址)。

2> 理想上我们希望有固定的存取时间。解决方案:

大部分编译器是经由拷贝操作取的所有的nested
virtual base class指针,放到derived class
object之中。

对象布局:

1 以指针指向base class的实现模型

2 使用virtual table offset strategy所产生的数据布局

data语意学,布布扣,bubuko.com

时间: 2024-10-27 07:55:39

data语意学的相关文章

《深度探索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.一般来说老的机器会是第一种结果,因为编

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

深度探索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++ data语意学

case1  :数据成员的绑定 extern float x; class Point3d { public: point3d(); //问题:被传回和被设定的x是哪一个x呢? float X() const { return x; } private: float x, y, z;//Point3d::X()将返回内部的x. }; 在早期(2.0之前)C++的编译器上,将会指向global x object, 导致C++的两种防御性程序设计风格:                1.把所有的da

【C++】深度探索C++对象模型读书笔记--Data语意学(The Semantics of data)

1. 一个空类的大小是1 byte.这是为了让这一类的两个对象得以在内存中配置独一无二的地址. 2. Nonstatic data member 放置的是“个别的class object”感兴趣的数据,static data members则放置的是“整个class”感兴趣的数据. 3. C++对象模型把nonstatic data members直接放在每一个classs object之中.对于继承而来的nonstatic data members(不管是virtual 还是nonvirtua

Data 语意学 —— 数据成员的绑定、布局与存取

数据成员的绑定 早期的 C++ 编译器为了能够正确绑定具体的数据成员,规定了以下两种行为: 所有的 data member 必须放在 class 声明的起始处: 所有的 inline functions 放在 class 声明之外: 从早期的编译器可以知道,绑定数据成员类型必须要在 class 声明完成之后才能确定,但是现在 C++ 编译器不必要要求上面的规定,也可以正确绑定具体的数据成员,例如: extern float x; class Pointer{ public: Pointer(fl

第三章、Data语意学

无虚继承的空类占一个字节,用于标记该类.有虚继承的空类至少占4个字节(可能继承的空类占很大空间).对齐情况class X{float i;//8char j;//1int k;//4double b;//下面重新的字节8,上面用来对齐};sizeof(X)=24 class Y{char j;//1int k;//4};sizeof(Y)=8 class Z{char j;//1int k;//4double b;};sizeof(Y)=16 1.Data Member的绑定有两种情况:情况一:

Data 语意学---Data member的存取效率

<深度探索C++对象模型> 对于data member来说,有两种情况 static data member数据 每一个static data member只有一个实体,存放在程序的data segment之中,无论以何种方式,无论类的继承关系如何复杂,存取路径都是非常直接 Nonstatic data members 直接存放在一个class object之中,是属于一个对象的,是需要一个叫做偏移量的值来索引的. 尤其是虚拟继承,虚拟继承将为"经由base class subobj