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。一般来说老的机器会是第一种结果,因为编译器比较老。

解释第一个问题:为什么X的sizeof会是1。这是因为无论老的编译器还是新的编译器,都会给空类X安插一个char,这样会保证类X的两个objects在内存中得以配置独一无二的地址。

注意:这里开始先说老编译器

第二个结论:老的编译器会对所有的空类安插一个char。这样Y的内存就有一个char(1字节),以及一个指向虚基类的指针(4字节),为了对齐需要,又加3个字节,所以Y总共是8个字节。同理Z也是8个字节。

第三个结论:class A的大小。A的大小由以下决定:X的大小+Y的单纯大小(意思是要减去为了满足虚继承基类X而配置的大小)+Z的单纯大小。注意X是虚基类,虚基类在派生体系中只会有一份实例,这样即使A继承了Y, 又继承了Z, 但是它只有一份X的实例(1个字节),加上对齐3个字节,所以是4个字节。Y的纯大小,Y的总大小是8个字节,为了满足虚继承基类X而配置的头部指针(4字节),那么单纯大小是4字节,Z的单纯大小也是4字节,所以A的大小12字节。这样就回答了老的编译器1,8,8,12的情况。

注意:这里开始说新的编译器1,4,4,8的情况。也是我自己测得的情况。

首先,类X的大小依旧是1字节。因为安插一个char。

其次,Y没有必要再安插一个char了。新的编译器将空的虚基类视为派生类的最开头的部分(这里这部分理解为Y的member),派生类Y在内存里已经至少有4个字节了,那么类Y所对应的objects在内存里已经可以区分开来,所以不需要再安插char了。这样Y和Z就都是4个字节了(都是为了满足虚继承而配置一个指针)。

最后,A的8个字节。因为A继承Y和Z,所以A的大小是YZ之和为8,同时class X的实例安插的char可以被拿去了,因为A在内存里已经不空了,所以A是8字节。

最终结论:一个类到底多大。答:容纳的所有的nonstatic data members(非静态数据成员),同时加上编译器为了支持某些语言特性而自动加上的额外的data members(如vptr)。最后加上alignment(边界对齐)的需要。

数据成员的绑定问题。

这里遇到这样的一个问题,代码如下:

extern floatx;
class Point3d{
public:
    Point3d(float,float,float);
    //问题:被传回和被设定的x是哪个x呢
    floatX() const { returnx; }
    voidX(float new_x)const{ x = new_x; }
    //..
private:
    floatx, y, z;
};

老式的编译器在处理时,遇到第一个x出现的那一行会把x解析为全局的那个x,这样就引起了一些错误。

新式的编译器是直到遇见类声明的右括号以后才对类的函数进行数据绑定操作。这样避免了数据的绑定错误。

然而新式的编译器在面临这样的数据类型的问题时(问题如下),

typedef intlength;
class Point3d{
public:
    //喔欧:length 第一次遇见,就被决议(resolved)为global
    //_val会被决议为Point3d::_val,这个没问题。
    voidmumble(length val){_val = val; }
    length mumble(){ return _val; }
    //..
private:
    typedeffloatlength;
    length _val;
    //..
};

在这样的问题里,成员的解析没有问题,因为新式的编译器等类全部声明完以后才解析_val,所以_val被正确解析为类内的_val。然而数据类型length却不能被正确解析为我们所想要的类的length. 这种情况下,即使在新式的编译器内,也应该采取防御性程序风格:总是把“nested type 声明”放在class 的起始处。这样可以确保绑定的正确性。

数据成员的布局问题。

看这样的一个类,他的数据成员怎么布局呢。

class Point3d{
public:
    // ..
private:
    float x;
    static List<Point3d*> *freeList;
    float y;
    static const int chunkSize = 250;
    float z;
};

答:所有静态数据成员都不会放在对象的布局之中,非静态数据成员在类对象中的排列顺序和其被声明的顺序一致。

另,如前所述,编译器会增加一些内部使用的数据成员,比如vptr。C++标准允许编译器随意放置这些内部产生出来的数据成员,一般情况下,有的编译器会把vptr放在类对象的最前端。

对section的处理,

例子代码如下:

class Point3d{
public:
    // ..
private:
    float x;
    static List<Point3d*> *freeList;
private:
    float y;
    static const int chunkSize = 250;
private:
    float z;
};

结论:编译器会把各个access section连锁在一起,依照声明的顺序连成一个区块。由此section的多少并不会带来的负担。即:在一个section里声明8个变量,和在8个section各声明一个变量,得到的对象的大小是一样的。

数据成员的存取差别问题。

即考虑他们

origin.x = 0.0;
pt->x = 0.0;

的存取差别。

对于静态数据成员:

静态的member其实并不在class object之中,因此存取静态的数据成员并不需要通过class object。所以存取一个静态数据成员时,不需要额外付出什么成本,它在程序的全局区,取出其地址直接存即可。小补充:如果有两个classes,他们都声明了一个static member freeList,那么如果他们都直接放在程序的data segment时,就会导致名称冲突。这时候编译器会暗中对每一个static data member编码,以获得一个独一无二的程序识别代码,这种方法统称name-mangling。

对于非静态的数据成员:

首先要知道,非静态的数据成员其实是有一个隐含的类对象指针,即this指针。对于非静态数据成员的存取操作,需要类对象的地址加上数据成员的偏移地址。数据成员的偏移地址在编译期即可获知,即使数据成员是经过派生派生派生等而来的,其偏移地址一样可以获得,由此存取origin.x时,只需对象origin的地址,和x的偏移,所以存取origin.x=0.0时,其效率和存取一个C struct member 或 一个 nonderived class的member是一样的。

虚拟继承时,情况是不同的,考虑以下代码:

Point3d *pt3d;
pt3d->_x = 0.0;

这样的话,当_x是一个struct member, 一个class member, 单一继承、多重继承的情况下都完全相同。但是如果 _x是一个virtual base class 的 member,存取速度会稍微慢一些。

Point3d origin, *pt=&origin;
origin.x = 0.0;
pt->x = 0.0;

有什么重大的差异?

答案:当Point3d是一个derived class(派生类),并且其继承结构里有一个virtual base class(虚基类),同时x就是从该virtual base class 继承而来的member时,会有重大差异。因为这时候我们不能说pt必然指向哪一种class type(因此,我们就不知道编译时期,这个member x的真正的偏移地址),所以这个存取操作必须延迟至执行期,经由一个额外的间接引导,才能够解决。但是如果用origin就不会出现这样的问题,其类型无疑是Point3d class, 而它继承自virtual base class 的 members的偏移地址,在编译时期就已经固定。用我的话说就是:pt被声明为一个Point3d 的指针,这没问题,但是pt可以指向其他的类类型的对象,比如指向Point3d的派生类的对象,或者派生类的派生类的对象等等。不同类类型的x的偏移位置是不同的,所以在编译期,看到了pt,只能看到他声明的是Point3d 的指针,但是不能根据Point3d类的x偏移位置去计算x的偏移位置。只能放在运行时去确定,所以在这种情况下,效率会减慢一些。这也是多态的一个代价(此句待验证)。

先到这,如有错误或不对之处,欢迎探讨和纠正。

时间: 2024-08-25 13:49:01

Data语意学里的一些问题的相关文章

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在程序执行时的某种

《深度探索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{};

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