深度探索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的指针,有些编译器会继承base的一个char数据,有些编译器会舍去。如果保持,则包含数据,且为5bytes,需要保持alignment,则为8bytes。Class A,同时继承Y和Z。// Visual C++和G++采取empty virtual base class被视为derived class object最开头的一部分,则derived class不需要耗费1char 来维持object的独一性。

G++ 上结果为 1,8,8,16 (由于sizeof(int *) = 8)

sizeof(Y) = sizeof(pointer in machine)

class W{char b;} sizeof(W) = 1;W w ; sizeof(w) = 1; 表示实际占用的大小,(针对alignment编译器已经做好了优化,不用考虑)

C++对象模型尽量以空间优化和存取速度的考虑来实现nonstatic data members。数据直接存放在每一个class object中。

class object 大小:

1) 实际的数据对象 non-static data member

2) 编译器自动加上的额外data member,用于支持语言特性(主要是virtual)

3) 因为alignment的需要(这个目前来说由底层实现,一般不用考虑,sizeof得到的是实际大小)

3.1 Data Member 的绑定

有意思的一章节,虽然了Class数据绑定的发展

extern float x;

typedef int type;

class A { public : float X(type val) const { return x;} private: float x;typedef float type;}

//抛出一个问题,函数X返回值是全局的x还是class内部的x?

当然,现在的C++模型肯定是内部的;而之前不一定,从前向后解析,并进行绑定;所以x被绑定到了全局。

现在进行了改进:只有当把class扫描完之后才对函数本体进行解析。并且遇到新的类型,就标记前一个标记为非法(如float x覆盖int x),用正确的解析。但对于参数,但对于参数仍可能绑定到全局变量(如type val,仍绑定到全局)。

因此我们仍需要采用防御性程序风格: 保证变量声明在定义之前,来确保非直觉绑定的正确性。

1) 总是把声明放到class的最开头

2)总是把inline function放在class体外部;

3.2 Data Member 的布局(Data Member Layout)

由编译器确定,一般规则相同的access section的数据放在同一个区域,也可以多个section放在同一个区域。

3.3 数据的存取

抛出一个问题:origin.x和pt->x 访问有什么区别? origin.x 主要是编译期间静态绑定,根据x的offset偏移量进行访问;而pt可能会不确定类型,需要运行时确定;

主要区别静态数据和非静态数据,静态数据保存在data segment段,可以被类直接访问,所有继承的类也都共享该静态对象,在内存中是唯一性;

为了避免多个静态变量的命名冲突,可以采用name-mangling才进行映射,保证唯一性,并可以反推回来。

非静态数据位只有object才可以访问,默认访问时需要有this(implicit class object)

有一个问题:本文中指出一个指向data member的指针,用以指出class的第一个member,和一个指向data member的指针,两者之间相差一个byte,类似于空class一样。但我在G++上测试,两者的基地址保持一致,并无1byte的相差;

包含virtual function的布局:有的vptr放在数据尾部,保持和struct兼容性;但目前普遍放在class的头部,便于访问virtual function。

3.4 继承与data member

单一继承,多重继承(不包含虚拟继承):按照继承class声明的顺序继承数据,当然继承顺序主要由编译器决定;

书中提到:多重继承中可能由于alignment导致多重继承之后class的size变大。但似乎在G++中,能够有效的优化,没有带来alignment的开销。(经测试)

主要难点是virtual inheritance,虚拟继承主要用来保持数据的唯一性,但也带来了复杂性和效率问题。不同的编译器有不同的处理方式。这里提到两种方法:

1)子类保存对虚拟继承父类的指针,随着虚拟继承嵌套层数的增多,会导致间接访问时间开销过大,需要多层间接访问;

2)为了避免上述情况,保持访问的一致性,将virtual function entries 和virtual base class offset放在一起;可以将虚拟继承的父类对象拷贝到自己的对象空间,同时保持对该空间的指针;

如果同时继承来自多个虚拟class,则仅保留一份对原始父类; (指向父类的指针放在class空间的开头) 【补图】

3.5 和 3.6 主要谈到效率问题和指向members指针

作者测试的环境和现在的不太一样,下一步在探究。

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

时间: 2024-10-26 14:39:30

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

【深度探索C++对象模型】第二章 构造函数语意学(上)

第二章 构造函数语意学(The Semantics of Constructors) -- 本书作者:Stanley B.Lippman 一.前言 首先让我们来梳理一个概念: 默认构造函数(Default Constructor) : 是在没有显示提供初始化式时调用的构造函数.它由不带任何参数的构造函数,或是为所有形参提供默认实参的构造函数定义.如果定义的某个类的成员变量没有提供显示的初始化式时,就会调用默认构造函数(Default Contructor). 如果用户的类里面,没有显示的定义任何

深度探索C++对象模型第6章 执行期语意学

(一)对象的构造和析构(Object Construction and Destruction) 一般而言我们会把object尽可能放置在使用它的那个程序区段附近,这么做可以节省非必要的对象产生操作和摧毁操作. 全局对象 如果我们有以下程序片段: Matrix identity main() { //identity 必须在此处被初始化 Matrix m1=identity; ... return 0; } C++保证,一定会在main()函数中第一次用到identity之前,把identity

《深度探索C++对象模型》第二章 | 构造函数语意学

默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一个成员对象,该成员对象拥有默认构造函数,那么这个类的隐式默认构造函数就是非平凡的,编译器需要为该类合成默认构造函数.为了避免合成出多个默认构造函数,编译器会把合成的默认构造函数.拷贝构造函数.析构函数和赋值拷贝操作符都以内联的方式完成.一个内联含有具有静态链接,不会被文件以外者看到.如果函数不适合做成内联,就

深度探索c++对象模型读书笔记:Data语意学-继承与Data member中内存对齐问题

书中在继承之后内存对齐问题上说道下面代码: 1 #include <bits/stdc++.h> 2 using namespace std; 3 class A 4 { 5 private: 6 int val; 7 char bit1; 8 }; 9 class B : public A 10 { 11 private: 12 char bit2; 13 }; 14 class C : public B 15 { 16 private: 17 char bit3; 18 }; 19 int

【深度探索C++对象模型】第一章 关于对象

第一章 关于对象(Object Lessons) -- 本书作者:Stanley B.Lippman 一.前言 什么是 C++ 对象模型:简单的说,就是 C++ 中面向对象的底层实现机制. 本书组织: 第 1 章,关于对象(Object Lessons),介绍 C++ 对象的基础概念,给读者一个粗略的了解. 第 2 章,构造函数语意学(The Semantics of Constructors),构造函数什么时候会被编译器合成?它给我们的程序效率带来了怎样的影响? 第 3 章,Data语意学(T

深度探索C++对象模型 第五章 构造、析构、拷贝语意学

1. const 成员函数需要吗? 尽量不要,如果存在继承,则无法预支子类是否有可能改变data member 2. pure virtual constructor 可以实现类的隐藏吗(包含data member)?   这样子类无法调用base 的构造函数对数据初始化,所以可以用protected来实现构造函数,可以实现子类调用: 3. 如果class中存在virtual function,则编译器会再构造函数中对vptr进行初始化(在base构造函数调用之后,而代码实现之前) 4.拷贝构造

Android深度探索-卷1第三章心得体会

第三章整章介绍了git,git是一个开源的分布式版本控制系统,用以有效.高速的处理从很小到非常大的项目版本管理.通过配置git后可以很方便的找到需要的资源,更多的是代码和包,可以在本地建立版本库,为了方便和尽可能多的获得资源,我觉得还是在网上的好,(所有网上,懂么)这就需要理解git的用法和功能听说git很复杂,书上也是用一个例子来演示了如何创建版本库.提交源代码.创建分支.向远程服务器上传源代码,从远程服务器获取源码.在上传自己的代码时,如果你不想让别人看见,你的缴费,不然就是开源的,就像我们

Android深度探索读书笔记 第三章

第三章主要介绍的是Git,首先是安装git可以使用这些命令(#apt-get inatall git    #apt-get inatall git-doc git-svn git-email git-gui gitl)来安装git,其中Git包含了大部分git命令是必须安装的软件包.其次是查看git文档:在Linux下可以直接使用man命令查看指定命令的帮助文档.如要查询git-checkout命令的帮助文档,可以使用#man git-checkout 接着是源代码的提交与获取:1创建版本库 

深度探索C++对象模型之第一章:关于对象之对象的差异

C++程序设计模型支持三种程序设计范式(programming paradiams). 程序模型(procedural model) char boy[] = "ccpang"; char *p_son; p_son = new char[strlen(boy) +1 ]; strcpy(p_son,boy); if(!strcmp(p_son,boy)) take_to_disneyland(boy); 抽象数据模型(abstract data type model) 此模型的抽象是