C++ 中类的内存布局
在许多笔试面试中都会涉及到sizeof 运算符的求值问题。
这类问题主要分四类:
- 基本数据类型,如int,bool,fload,long,long,int * 等,这一类比较简单,但要注意x86和x64情况下的指针大小
- 枚举 enum。这个类型网络上有说是1-4个byte,根据最大值决定的;也有说是sizeof(int)。我这边个人使用 visual studio 2015 获得的结果是4个byte
- struct 和 union 组合类型。union 是取其中一个最大成员的size作为其size;struct 则要考虑对齐填充因素
- class 类型,class 就稍微复杂点,不仅仅要考虑对齐填充因素,还要考虑继承,虚继承,虚函数等因素。
下文主要讲述class 的内存布局,稍带介绍一下struct 的size。
struct 的内存布局:
struct 的内存对齐和填充概念学过C 的都应该知道一点。其实只要记住一个概念和三个原则就可以了:
一个概念:
自然对齐:如果一个变量的内存地址正好位于它长度的整数倍,就被称做自然对齐。
如果不自然对齐,会带来CPU存取数据时的性能损失。(PS:具体应该与CPU通过总线读写内存数据的细节相关,具体没有细究)
三个原则:
- struct 的起始地址需要能够被其成员中最宽的基本数据类型整除;
- struct 的size 也必须能够被其成员中最宽的基本数据类型整除;
- 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++ 中类的内存布局相关推荐
- vs开发人员命令查看C++类 data member 内存布局
C++中类的数据成员在内存中时如何分布的,有继承,虚拟继承等情况下又是怎么分布的?在VS编译器中可以查看. 源代码如下: #include<iostream> using namespac ...
- JVM成神路对象内存布局、分配过程、从生至死历程、强弱软件引用
引言 对象实例的角度,阐述一个Java对象从生到死的历程.Java对象在内存中的布局以及对象引用类型. 一.Java对象在内存中的布局 Java源代码中,使用new关键字创建出的对象实例,我们都知道在 ...
- (五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析
引言 在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内.而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程.J ...
- [C++对象模型][6]sizeof与对象内存布局
有了前面几节的铺垫,本节开始摸索C++的对象的内存布局,平台为windows32位+VS2008. 一 内置类型的size 内置类型,直接上代码,帮助大家加深记忆: Code void TestBas ...
- slub object 内存布局
我在 https://blog.csdn.net/wowricky/article/details/83218126 介绍了一种内存池,它的实现类似于linux 中打开slub_debug (1. m ...
- linux内存布局及页面映射
在Linux系统中,以32bit x86系统来说,进程的4GB内存空间(虚拟地址空间)被划分成为两个部分 ------用户空间和内核空间,大小分别为0-3G,3-4G. 用户进程通常情况下,只能访问用 ...
- 【C++】C++对象模型:对象内存布局详解(C#实例)
C++对象模型:对象内存布局详解 0.前言 C++对象的内存布局.虚表指针.虚基类指针解的探讨,参考. 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个概念可 ...
- 理解Java对象:要从内存布局及底层机制说起,话说....
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 前言 大家好,又见面了,今天是JVM专题的第二篇文章,在上 ...
- 多重继承和虚继承的内存布局
这篇文章主要讲解虚继承的C++对象内存分布问题,从中也引出了dynamic_cast和static_cast本质区别.虚函数表的格式等一些大部分C++程序员都似是而非的概念.原文见这里 (By Eds ...
最新文章
- 自己理解接口回调入门
- 记录一下g++的编译选项
- 人工神经网络是如何实现存算一体的
- Windows原生运行Linux的技术细节
- Linux Daemon Writing HOWTO
- asp调用php函数,asp函数split()对应php函数explode()
- 如何从数学角度解释何恺明新作Masked Autoencoders (MAE)?
- 随笔 面试题网站
- cURL在Web渗透测试中的应用
- 11月碎碎念-谈职场礼貌
- 作者:黄玲玲(1982-),女,博士,安徽省公共交通安全科学研究院副研究员。...
- Java 8 Stream API详解
- java 通过类名创建类,通过类名动态生成对象
- 微信h5游戏如何在微信中做好域名防封 防屏蔽的 工作
- 上市集团计算离职率sql案例
- 微软azure和亚马逊服务器,云计算两强,亚马逊AWS与微软Azure的差异!
- 传说之下三重审判用计算机怎么弹,传说之下三重审判模拟器
- Typora免费版本0.11.18
- php 微信获取门店列表,【转载】微信公众号获取用户地理位置并列出附近的门店...
- jQuery toggle 使用
热门文章
- 通过MageUi.exe修改通过ClickOnce发布过的WPF browser application 配置文件
- shell中#*,##*,#*,##*,% *,%% *的含义及用法
- Caffe实践】如何利用Caffe训练ImageNet分类网络
- laravel 5.5 整合 jwt 报错Method Tymon\JWTAuth\Commands\JWTGenerateCommand::handle() does not exist解决...
- 2. Dubbo和Zookeeper的关系
- iOS安全攻防(十七):Fishhook
- Hibernate5-唯一查询和聚合查询
- Android 混淆打包
- [图示]营销理论:不同时代的用户如何接受一个新事物?
- ElasticSearch之Java Api 测试