在许多笔试面试中都会涉及到sizeof 运算符的求值问题。

这类问题主要分四类:

  1. 基本数据类型,如int,bool,fload,long,long,int * 等,这一类比较简单,但要注意x86和x64情况下的指针大小
  2. 枚举 enum。这个类型网络上有说是1-4个byte,根据最大值决定的;也有说是sizeof(int)。我这边个人使用 visual studio 2015 获得的结果是4个byte
  3. struct 和 union 组合类型。union 是取其中一个最大成员的size作为其size;struct 则要考虑对齐填充因素
  4. class 类型,class 就稍微复杂点,不仅仅要考虑对齐填充因素,还要考虑继承,虚继承,虚函数等因素。

下文主要讲述class 的内存布局,稍带介绍一下struct 的size。

struct 的内存布局:

struct 的内存对齐和填充概念学过C 的都应该知道一点。其实只要记住一个概念和三个原则就可以了:

一个概念:

  自然对齐:如果一个变量的内存地址正好位于它长度的整数倍,就被称做自然对齐。

  如果不自然对齐,会带来CPU存取数据时的性能损失。(PS:具体应该与CPU通过总线读写内存数据的细节相关,具体没有细究)

三个原则:

  1. struct 的起始地址需要能够被其成员中最宽的基本数据类型整除;
  2. struct 的size 也必须能够被其成员中最宽的基本数据类型整除;
  3. struct 中每个成员地址相对于struct 的起始地址的offset,必须是自然对齐的。

Class 的内存布局:

在学习C++ 的class 的内存布局前,先介绍下文会被用到的Visual studio 中的编译选项"/d1reportAllClassLayout" 和 "/d1reportSingleClassLayout[ClassName]"。

这两个编译选项分别会输出当前编译单元中所以class 的内存布局和指定class 的内存布局。对于学习class 的内存布局很方便。

关于一个class 的定义,在定义过程中涉及到的有:

  成员数据(静态,非静态)和成员函数(静态,非静态,virtual)。

所有的成员函数都不会占用对象的存储空间,无论是静态,非静态还是虚函数。

而对于成员数据来说,只有非静态的数据才会占用对象的存储空间。

这个很好理解,静态成员数据和成员函数是属于class 的,而非属于具体的对象,所以只要维护一份内存就可以了,无需每个对象都拷贝一份。

但是影响对象的大小的因素并不仅仅与看到的成员变量有关:

  非静态成员变量,虚函数表指针(_vftprt),虚基类表指针(_vbtptr),上文的内存对齐

  • 空类

class CEmpty{};

  对于空类,许多人想当然的认为大小应该是0。这是错误的,如果是正确的话,这个类可以被实例化成一个对象,且这个对象不占任何存储空间,且可以有很多不占任何空间的对象,而且这个不占空间的对象还可以有指针,这样就很奇怪了。

  所以正常编译器会给空类分配1个byte 的空间用于标示。

  sizeof(CEmpty) = 1

  • 普通类

class CBase {
public:int m_ia;static int s_ib;
private:void f();void g();
};

  其类的布局如下:

class CBase  size(4):+---0  | m_ia+---

  只有m_ia 成员,size 为4个byte。因为静态数据成员和成员函数不占有对象空间。

  • 有虚函数的类

class CBase {
public:int m_ia;
private:void f();void g();virtual void h();
};

  其类的布局如下:

class CBase  size(8):+---0  | {vfptr}4  | m_ia+---CBase::$vftable@:| &CBase_meta|  00 | &CBase::h

  可以看到该类的起始地址是放了一个"vfptr",这个指针用来指向该类的虚函数表。

  • 单一继承的类(无虚函数)

class CBase {
public:int m_ia;
private:void f();void g();
};class CChild :public CBase {
public:int m_iChild;
};

  类的布局如下:

class CChild  size(8):+---| +--- (base class CBase)0    | | m_ia| +---4    | m_iChild+---

  即派生类中拷贝了一份基类中的成员数据,所以size 为8个byte。

  • 单一继承的类(含有虚函数)

class CBase {
public:int m_ia;
public:virtual ~CBase();virtual void f();virtual void g();
};class CChild :public CBase {
public:int m_iChild;
public:virtual ~CChild();virtual void g();
};

  其类的布局如下:

class CChild size(12):+---| +--- (base class CBase)0   | | {vfptr}4    | | m_ia| +---8    | m_iChild+---CChild::$vftable@:| &CChild_meta|  00   | &CChild::{dtor} 1 | &CBase::f 2   | &CChild::g

  可以看到派生类中只有一个"vfptr",但是虚函数表中的函数却不同于基类中的函数,没有重写的虚函数沿用基类中的虚函数,而被重写的虚函数则更新为派生类中的虚函数。

  • 多重继承的类(基类都含有虚函数)

  

class CBase1 {
public:int m_i1;
public:virtual ~CBase1();virtual void f1();virtual void g1();
};class CBase2 {
public:int m_i2;
public:virtual ~CBase2();virtual void f2();virtual void g2();
};class CChild :public CBase1, public CBase2 {
public:int m_iChild;
public:virtual ~CChild();virtual void f1();virtual void g2();
};

  其类的布局如下:

class CChild size(20):+---| +--- (base class CBase1)0  | | {vfptr}4    | | m_i1| +---| +--- (base class CBase2)8 | | {vfptr}
12  | | m_i2| +---
16  | m_iChild+---CChild::$vftable@CBase1@:| &CChild_meta|  00   | &CChild::{dtor} 1 | &CChild::f1 2 | &CBase1::g1 CChild::$vftable@CBase2@:| -80  | &thunk: this-=8; goto CChild::{dtor} 1   | &CBase2::f2 2 | &CChild::g2

  CChild 分别从CBase1 和 CBase 中继承一个vfptr.

  • 菱形结构继承的类(非虚继承)

class CBase {
public:int m_iBase;
public:virtual ~CBase();virtual void f0();virtual void g0();virtual void h0();
};class CChild1:public CBase {
public:int m_iChild1;
public:virtual ~CChild1();virtual void f0();virtual void h1();
};class CChild2:public CBase {
public:int m_iChild2;
public:~CChild2();void g0();void h1();
};class CGrandChild :public CChild1, public CChild2 {
public:int m_iGrandChild;
public:virtual ~CGrandChild();virtual void h0();virtual void h1();virtual void h2();virtual void f0();
};

  其类的布局如下:

class CGrandChild    size(28):+---| +--- (base class CChild1)| | +--- (base class CBase)0 | | | {vfptr}4  | | | m_iBase| | +---8 | | m_iChild1| +---| +--- (base class CChild2)| | +--- (base class CBase)
12  | | | {vfptr}
16  | | | m_iBase| | +---
20  | | m_iChild2| +---
24  | m_iGrandChild+---CGrandChild::$vftable@CChild1@:| &CGrandChild_meta|  00   | &CGrandChild::{dtor} 1    | &CGrandChild::f0 2    | &CBase::g0 3  | &CGrandChild::h0 4    | &CGrandChild::h1 5    | &CGrandChild::h2 CGrandChild::$vftable@CChild2@:| -120  | &thunk: this-=12; goto CGrandChild::{dtor} 1 | &thunk: this-=12; goto CGrandChild::f0 2 | &CChild2::g0 3    | &thunk: this-=12; goto CGrandChild::h0

  这种继承是有风险的,即通过CGrandChild 去访问m_iBase 时,容易造成二义性,需要使用"pGrandChild->CChild::m_iBase" 这种方法去访问。

  为了避免这种问题,C++ 中有一种机制是虚继承

  • 单一虚继承

class CBase {
public:int m_iBase;
public:virtual ~CBase();virtual void f0();virtual void g0();virtual void h0();
};class CChild1: virtual public CBase {
public:int m_iChild1;
public:virtual ~CChild1();virtual void f0();virtual void h1();
};

  其类的布局如下:

class CChild1    size(24):+---0 | {vfptr}4  | {vbptr}8  | m_iChild1+---
12  | (vtordisp for vbase CBase)+--- (virtual base CBase)
16  | {vfptr}
20  | m_iBase+---CChild1::$vftable@CChild1@:| &CChild1_meta|  00 | &CChild1::h1 CChild1::$vbtable@:0    | -41   | 12 (CChild1d(CChild1+4)CBase)CChild1::$vftable@CBase@:| -160   | &(vtordisp) CChild1::{dtor} 1 | &(vtordisp) CChild1::f0 2 | &CBase::g0 3  | &CBase::h0

  从布局中看,发现多了一个vbptr 指针,则是一个指向基类的虚基类指针;在派生类和虚基类之间又多了“vtordisp for vbase CBase”,vtordisp 并不是每个虚继承的派生类都会生成的,关于这部分可以参考MSDN 中 vtordisp;在vtordisp 后面则是虚基类的一个拷贝。

  • 多重继承的类(虚继承)

class CChild1 {
public:int m_iChild1;
public:virtual ~CChild1();virtual void f0();virtual void h1();
};class CChild2 {
public:int m_iChild2;
public:~CChild2();void g0();void h1();
};class CGrandChild :public CChild1, public CChild2 {
public:int m_iGrandChild;
public:virtual ~CGrandChild();virtual void h0();virtual void h1();virtual void h2();virtual void f0();
};

  virtual public Child1, public CChild2

    其类的布局如下:

class CGrandChild   size(28):+---0 | {vfptr}| +--- (base class CChild2)4  | | m_iChild2| +---8   | {vbptr}
12  | m_iGrandChild+---
16  | (vtordisp for vbase CChild1)+--- (virtual base CChild1)
20  | {vfptr}
24  | m_iChild1+---

  

  public Child1, virtual public CChild2

    其类的布局如下:

class CGrandChild size(20):+---| +--- (base class CChild1)0 | | {vfptr}4    | | m_iChild1| +---8   | {vbptr}
12  | m_iGrandChild+---+--- (virtual base CChild2)
16  | m_iChild2+---

  virtual public Child1, virtual public CChild2

class CGrandChild  size(28):+---0 | {vfptr}4  | {vbptr}8  | m_iGrandChild+---
12  | (vtordisp for vbase CChild1)+--- (virtual base CChild1)
16  | {vfptr}
20  | m_iChild1+---+--- (virtual base CChild2)
24  | m_iChild2+---

  通过上述虚继承的情况来看,可以看出有虚继承的派生类中,派生类和虚基类的数据是完全隔开的,先存放派生类自己的虚函数指针,虚基类指针和数据;然后有vtordisp 作为间隔;在存放虚基类的内容。

  • 菱形结构继承的类(虚继承)

class CBase {
public:int m_iBase;
public:virtual ~CBase();virtual void f0();virtual void g0();virtual void h0();
};class CChild1 : virtual public CBase {
public:int m_iChild1;
public:virtual ~CChild1();virtual void f0();virtual void h1();
};class CChild2 : virtual public CBase{
public:int m_iChild2;
public:virtual ~CChild2();virtual void g0();virtual void h1();
};class CGrandChild : public CChild1, public CChild2 {
public:int m_iGrandChild;
public:virtual ~CGrandChild();virtual void h0();virtual void h1();virtual void h2();virtual void f0();
};

  其类的布局如下:

class CGrandChild    size(40):+---| +--- (base class CChild1)0 | | {vfptr}4    | | {vbptr}8    | | m_iChild1| +---| +--- (base class CChild2)
12  | | {vfptr}
16  | | {vbptr}
20  | | m_iChild2| +---
24  | m_iGrandChild+---
28  | (vtordisp for vbase CBase)+--- (virtual base CBase)
32  | {vfptr}
36  | m_iBase+---

  有了上文的基础,这个派生类的机构就不难理解了。

  

转载于:https://www.cnblogs.com/jiaochen/p/5524335.html

C++ 中类的内存布局相关推荐

  1. vs开发人员命令查看C++类 data member 内存布局

    C++中类的数据成员在内存中时如何分布的,有继承,虚拟继承等情况下又是怎么分布的?在VS编译器中可以查看. 源代码如下: #include<iostream> using namespac ...

  2. JVM成神路对象内存布局、分配过程、从生至死历程、强弱软件引用

    引言 对象实例的角度,阐述一个Java对象从生到死的历程.Java对象在内存中的布局以及对象引用类型. 一.Java对象在内存中的布局 Java源代码中,使用new关键字创建出的对象实例,我们都知道在 ...

  3. (五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析

    引言 在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内.而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程.J ...

  4. [C++对象模型][6]sizeof与对象内存布局

    有了前面几节的铺垫,本节开始摸索C++的对象的内存布局,平台为windows32位+VS2008. 一 内置类型的size 内置类型,直接上代码,帮助大家加深记忆: Code void TestBas ...

  5. slub object 内存布局

    我在 https://blog.csdn.net/wowricky/article/details/83218126 介绍了一种内存池,它的实现类似于linux 中打开slub_debug (1. m ...

  6. linux内存布局及页面映射

    在Linux系统中,以32bit x86系统来说,进程的4GB内存空间(虚拟地址空间)被划分成为两个部分 ------用户空间和内核空间,大小分别为0-3G,3-4G. 用户进程通常情况下,只能访问用 ...

  7. 【C++】C++对象模型:对象内存布局详解(C#实例)

    C++对象模型:对象内存布局详解 0.前言 C++对象的内存布局.虚表指针.虚基类指针解的探讨,参考. 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个概念可 ...

  8. 理解Java对象:要从内存布局及底层机制说起,话说....

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 前言 大家好,又见面了,今天是JVM专题的第二篇文章,在上 ...

  9. 多重继承和虚继承的内存布局

    这篇文章主要讲解虚继承的C++对象内存分布问题,从中也引出了dynamic_cast和static_cast本质区别.虚函数表的格式等一些大部分C++程序员都似是而非的概念.原文见这里 (By Eds ...

最新文章

  1. 自己理解接口回调入门
  2. 记录一下g++的编译选项
  3. 人工神经网络是如何实现存算一体的
  4. Windows原生运行Linux的技术细节
  5. Linux Daemon Writing HOWTO
  6. asp调用php函数,asp函数split()对应php函数explode()
  7. 如何从数学角度解释何恺明新作Masked Autoencoders (MAE)?
  8. 随笔   面试题网站
  9. cURL在Web渗透测试中的应用
  10. 11月碎碎念-谈职场礼貌
  11. 作者:黄玲玲(1982-),女,博士,安徽省公共交通安全科学研究院副研究员。...
  12. Java 8 Stream API详解
  13. java 通过类名创建类,通过类名动态生成对象
  14. 微信h5游戏如何在微信中做好域名防封 防屏蔽的 工作
  15. 上市集团计算离职率sql案例
  16. 微软azure和亚马逊服务器,云计算两强,亚马逊AWS与微软Azure的差异!
  17. 传说之下三重审判用计算机怎么弹,传说之下三重审判模拟器
  18. Typora免费版本0.11.18
  19. php 微信获取门店列表,【转载】微信公众号获取用户地理位置并列出附近的门店...
  20. jQuery toggle 使用

热门文章

  1. 通过MageUi.exe修改通过ClickOnce发布过的WPF browser application 配置文件
  2. shell中#*,##*,#*,##*,% *,%% *的含义及用法
  3. Caffe实践】如何利用Caffe训练ImageNet分类网络
  4. laravel 5.5 整合 jwt 报错Method Tymon\JWTAuth\Commands\JWTGenerateCommand::handle() does not exist解决...
  5. 2. Dubbo和Zookeeper的关系
  6. iOS安全攻防(十七):Fishhook
  7. Hibernate5-唯一查询和聚合查询
  8. Android 混淆打包
  9. [图示]营销理论:不同时代的用户如何接受一个新事物?
  10. ElasticSearch之Java Api 测试