深度探索C++对象模型 Data语意学笔记

Date (一)Date 获取系统当前的时间:var now=new Date(); now; //TusFeb 21 2017 19:49:22 GMT+0800 (CST) now... 阅读详情

class X { };

class Y : public virtual X { };

class Z : public virtual X { };

class A : public Y, public Z { };

它们的sizeof结果如下:

sizeof(X) = 1;

sizeof(Y) = 8; //视编译器不同而不同

sizeof(Z) = 8; //视编译器不同而不同

sizeof(A) = 12;

 

实际上,class X 并不是空,它有一个晦涩的1bytes,那是被编译器安插进去的一个char, 这使得这个class的两个对象将在内存中有不同的地址。

 

而class Y 和 class Z 与三个因素有关。

一:语言本身造成的额外负担。当语言支持virtual base class 时,会导致一些额外的负担。在派生类中,这个额外负担反映在指针身上,它或者是指向virtual base class subobject;或者指向一个表格,表格中存放的若不是virtual base class subobject的地址,就是其偏移量。

二:编译器对于特殊情况所提供的优化处理。Virtual base class X 的subobject的1bytes大小也出现在class Y 和 Z 身上。传统上它被放在派生类固定部分的尾端。而某些编译器会对empty virtual base class 提供特殊支持。

三:Alignment 的限制。Class Y 和 Z 目前为5字节,为了进行对齐,将会被变成8字节。

 

而在某些编译器中,比如visual C++中,结果为1,4,4,8。这是因为编译器提供了特殊处理。它的模型如下:

对于Class A,它的大小是多少呢?由以下决定(一个虚拟继承的基类只有一个实体,不管被继承了多少份)

一:被大家共享的唯一一个class X 实体,1个字节。

二:Base Y 的大小,为4字节。Z也一样。

三:Class A 自己的大小0字节。

四:alignment的情况。这个时候为9字节,为了对齐,所以为12字节。

而visual C++进行特殊处理后,Base Y + Base Z 的大小为8字节。

 

每一个静态数据都只有一个实体,存放在程序的data segment中,每次程序取用static member,就会被内部转化为对该唯一的extern实体的直接参考操作:

origin. chunksize = 250 等价于 Point3d::chunksize = 250。

pt->chunksize = 250 等价于 Point3d::chunksize = 250。

在C++中,这是“通过一个指针和通过一个对象来存取数据成员时,结论完全相同”的唯一一种情况因为此时的member并不在对象中

 

Point3d origin, *pt = &origin;

origin.x = 0.0

pt->x = 0.0

"从origin存取“和”从pt存取'有什么重大的差异吗? 答案是“当Point3d是个派生类,而x数据成员是基类的数据成员时,就有重大的差异。对于指针访问数据而言,这个操作必须延迟到执行时期进行访问。而如果使用origin,则在编译时期就确定了。

 

class Concrete

{

    private:

    int val;

    char c1;

    char c2;

    char c3;

};

 

sizeof(Concrete) = sizeof(val) + sizeof(char)*3 + alignment = 8;

 

class Concrete1

{

    private:

    int val;

    char c1;

}; 

 

class Concrete2:public Concrete1

{

  private:

  char c2;

};

 

class Concrete3: public Concrete2

{

 private:

  char c3;

};

 

这个时候,sizeof(Concrete3)并不等于8。

sizeof(Concrete3) = sizeof(Concrete1) + sizeof(Concrete2) + sizeof(Concrete3) = 8 + 4 + 4 = 16。

因为虚拟继承只有一个基类,如何对虚拟继承的基类进行布局呢?一种方法是把派生类分成两个部分:一个不变局部和一个共享局部。

虚拟继承图

     一般的布局策略是先安排好derived class 的不变部分,然后再建立其共享部分。那么如何存取class的共享部分呢?cfront 编译器会在每一个派生类对象中安插一些指针,每个指针指向一个virtual base class。这有两个问题:

一:每一个对象必须针对其每一个虚基类背负一个额外的指针。

二:由于虚拟继承串链的加长,导致间接存取层次的增加。这里的意思是:如果我有三层虚拟,我就需要三次间接存取。

    MetWare和其它编译器对于第二个问题的解决方法是:复制嵌套的虚拟类的指针放到派生类对象中,虽然付出了一些空间上的代价,但是访问时间不会随着继承的增加而增加时间。

    至于第一个问题,一般而言有两个解决方法。Microsoft编译器引入所谓的virtual base class table。virtual base class指针放在这个表格中。第二个解决方法是:是在virtual function table中放置virtual base class的offset。  

 

class Point3d

{

public:

    virtual ~Point3d();

    static Point3d origin;

    float x, y, z;

};

    那么& Point3d::z; 得到的是什么? 将得到z 在类对象中的偏移量。如果vptr放在对象的起头,则三个坐标值在对象布局中的offset分别是4, 8 , 12。然而我们若去取data members的地址时, 传回的值应该是多1的, 也就是1, 5, 9。因此:

printf("%p/n", &Point3d::x);

printf("%p/n", &Point3d::y);

printf("%p/n", &Point3d::z);

在VC6.0中编译得到的结果是:

    00000004

    00000008

    0000000C

在BCB3中编译得到的结果是:

    00000005

    00000009

    0000000D

这说明vptr放在编译器的起头。VC6.0得到的结果可能进行了特殊处理。好!现在回到正题,为什么值要加1呢?看下面这个例子。

float Point3d::*p1 = 0;

float Point3d::*p2 = &Point3d::x;

if( p1 == p2 )

{

    cout << "p1 & p2 contain the same value --";

    cout << " they must address the same member!" << endl;

}

 为了区分p1和p2, 每一个真正的member offset值都被加上1。 因此,不论编译器或使用者都必须记住,在真正使用该值以指出一个member之前,请先减掉1。

为了测试VC6.0的值为什么没有加1,我测试程序如下:

 

class Point3d

{

public:

  static Point3d origin;

  float x, y, z;

};

 

 

 

int main()

{

    float Point3d::*p1 = 0;

    float Point3d::*p2 = &Point3d::x;    

    if( p1 == p2 )

    {

      cout << "p1 & p2 contain the same value --";

      cout << " they must address the same member!" << endl;

    }  

    return 0;

}

发现p1的值为0xffffffff, p2的值为0x00000000。不执行输出。

if( p1 == NULL )

{

      cout << "p1 & p2 contain the same value --";

 

      cout << " they must address the same member!" << endl;    

}

p1的值为0xffffffff, 执行输出。

但是NULL的值为0啊!为什么会执行输出呢?

我的猜测是VC6.0对指针的值还是加1了,打印出来的结果是减1后的。

因此:

    & Point3d::z; 和 & origin.z之间的差异,就非常明确了。前者取”它在类中的偏移量,而后者取类对象中z的真正的地址“。


 

 

C++ 对象模型 第三章 Data语意学 对于下述类和继承计算上述每个类所占内存空间如下对于上述每个类的大小的讨论一个空的class,如x,sizeof(X)==1。事实上虽然X中无任何数据,但他有一个隐晦的1byte,那是被编译器安插进去的一个char,这使得这个类的两个对象得以在内存中配置独一无二的地址。x和y和z的对象布局接下来计算A的大小正常下,A应该是16bytes,但确是12bytes一个虚基类子对象只会在派生类中存在一份实体,不管他在类的继承体系中出现了多少此,类A的大小由如下几点决定。... 阅读详情

相关推荐

深度探索C++对象模型》读书笔记第三章:Data语意学

深度探索C++对象模型》读书笔记Data语意学 类的大小(sizeof) 一个空类的大小通常为1,是因为编译器为其安插了一个char,以便这个类的任意两个object能够在内存中配置独一无二的地址。 一个类的大小通常与机器和编译期有关,受以下三个因素的影响: 1. 语言本身的额外负担 比如支持虚函数,或者虚继承。 2. 编译器对于特殊情况的优化处理 某些编译器会对empty...

yhcuac 1082

深度探索C++对象模型》读书笔记(2)

***class大小的计算所引发的思考***在visual C++ 6.0中测试如下代码:#include "iostream"using namespace std;class X ...{};class Y : public virtual X ...{};class Z : public virtual X ...{};class A : public Y,public Z ..

技术资料库 1811

深度探索C++对象模型之第三章:data语义学中对象布局

说明: 看到  http://www.roading.org/?p=545 已经整理的很好了,所以前部分大部分引用原文,感谢原作者@~@,后半部分主要引用自书本 类对象占用的内存空间可能比我们想象的要大,有如下原因: (1) 由编译器自动加上额外的data memebers,用以支持某些语言特性(主要是各种virtual特性) (2)因为边界调整(alignment)的需要(详细请看:内存对

菜根 822

深度探索C++对象模型笔记 [2] 数据(Data)语意学

本文主要参考Stanley B.Lippman所著《Inside the C++ Object Model》,侯捷译。 前言 一个表面看上去是空的类,其实并不是空的!因为编译器为它安插了一个隐藏的char,这样使得这个类被实例化后,每个对象都有独一无二的地址(即这个char的地址)。 一个classdata members,一般而言,可以表现出class在程序执行时的某种状态。Nonsta...

Alfeim的博客 522

深度探索C++对象模型》学习笔记Data语意学

在写这个之前,我为毛觉得莫名激动,为何上篇文章阅读量都破三百了,难道是提到了什么了不得的关键字啊啊哈哈哈我不啰嗦了 你觉得一下代码结果是什么呢?#include<iostream> using namespace std; class A { }; class Y:public virtual A {}; class X:public virtual A {}; class Z:public X,

kidck的专栏 777

深度探索C++对象模型》阅读笔记 第三章 Data语意学

Lippman的一个法国读者的结果是: 而我在VS2019,win10,64bit上以及Linux环境下的Ubuntu下输出都是 https://www.cnblogs.com/fengyaoyao/p/10262312.htmlhttps://blog.csdn.net/yi_chengyu/article/details/120856687先贴几个重要结论:C++的空类是指这个类不带任何数据,即类中没有非静态(non-static)数据成员变量,没有虚函数(virtual function),也没有虚基

哇咔咔的博客 248

[读书笔记] - 《深度探索C++对象模型》第3章 Data语意学

一个空的class,如: // sizeof X == 1 class X { }; 事实上并不是空的,它有一个隐晦的1 byte,那是被编译器安插进去的一个char。这使得这个class的两个objects得以在内存中配置独一无二的地址。 1.Data Memeber的绑定 在下面的程序中,length的类型在两个member function signatuures中都解析为global typedef,也就是int。当后续再有length的nested typedef声明出现时,C++ St

浮生的博客 386

深度探索C++对象模型Data语意学读书笔记

深度探索C++对象模型Data语意,简单总结GCC和VC++的异同

Ron的专栏 930

深度探索C++对象模型-- Data 语意学(阅读笔记)(原创)

深度探索C++对象模型(阅读笔记)(原创) Inside The C++ Object Model                                                                --Data 语意学由 王宇 原创并发布 : 让我肃然起敬并崇拜的牛人:       Bjarne Stroustrup    -  C++之父    (www....

MrWangyu的专栏 192

深度探索C++对象模型》读书笔记Data语意学

1、  Data member的布局是怎样的? 非静态数据成员 1>    在同一个Access Section(也就是private,public,protected等区段)中,较晚出现的数据成员在classobject中有较高的地址。 2>    允许编译器将同一类型的多个Acess Section的顺序自由排列,而不必在乎它们的声明次序(但似乎没有编译器这样做)。

冰枫的随笔 1437

深度探索C++对象模型 学习笔记 第三章 Data语意学(1)

传统上,它被放在类所有显式声明的成员之后。Point2d 和 Point3d 类的声明与使用方式,与它们作为两个独立类时相比并无变化——因此,使用这些抽象的程序员无需关心这些对象究竟是相互独立的类类型,还是通过继承关联起来的。在这种情况下,我们无法确定 pt 实际指向的是哪种具体的类类型(因此也就无法在编译时得知该成员的实际偏移量),所以对该成员的访问必须通过额外的间接层推迟到运行时才能解析。例如,在一个访问段中声明八个成员,与在八个独立的访问段中分别声明这些成员,在实践中生成的类对象大小是完全相同的。

tus00000的博客 407

深度探索C++对象模型》学习笔记

深度探索C++对象模型》学习笔记 最近学习了c++对象模型,做下笔记,方便日后查阅 第一章:关于对象 c++对象模型 在此模型中,成员变量被配置在每一个对象中,静态成员变量、成员函数和静态成员函数放置在对象之外,虚函数则以下两个步骤支持: 每个类产生一堆指向虚函数的指针放在表格之中,这个表格叫做虚函数表(vtal) 每个对象中被添加一个指针(vptr),指向相关的虚函数表,vptr的赋值和重置由每个类的构造函数、析构函数和拷贝赋值运算符自动完成。每个类所关联的type_info放在虚函数表的表头

QT练手小程序 459

深度探索C++对象模型学习笔记——Data语意学

例子:class X{};class Y: public virtual X {};class Z: public virtual X {};class A: public Y, public Z {}; 对象大小由下述原因决定1.语言本身所造成的额外负担如:支持virtual base classes时,derived class中,额外负担反映在某种形式的指针身上,它或者指向vir

hong201的专栏 739

深度探索C++对象模型》读书笔记:第三章 Data语意学

3.1 Data Member的绑定 1.对于内联函数函数体内的data member绑定操作是在整个class声明完成之后才发生的,所以对于: extern float x; class Point3d { public: Point3d(float, float, float); float X() const { return x; } private: float x, y,...

junya_zhang的博客 250

深度探索C++对象模型读书笔记】【第3章】Data语意学

1、例子,用VS 2013编译器 #include<iostream> using namespace std; class X{}; class Y :public virtual X{}; class Z :public virtual X{}; class A :public Y, public Z{}; int main(){ cout << siz...

weixin_30790841的博客 139

深度探索C++对象模型》读书笔记——Data语意学【for_wind】

//整理之,分享之,欢迎指正。for_wind 1、类的实际大小。       每一个class object必须有足够的大小以容纳它所有的nonstatic data members。除了其nonstatic data members的总和大小外,其值的大小受以下三个因素影响: (1)语言本身所造成的额外负担:即为了支持virtual而由内部产生的额外负担。当语言支持virtual

for_wind的专栏 1394
上一篇: 深度探索C++对象模型 关于对象的笔记
下一篇: 深度探索C++对象模型 Function语意学笔记
caianye
博客等级 码龄19年 82粉丝 77原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值