总结:1、按1继承顺序先排布基于每个父类结构。2、该结构包括:基于该父类的虚表、该父类的虚基类表、父类的父类的成员变量、父类的成员变量。3、多重继承且连续继承时,虚函数表按继承顺序排布函数与虚函数。4、而后排布子类的成员变量。5、排布虚基类的虚函数表。6、虚基类的成员变量

#类中的元素

0. 成员变量   1. 成员函数   2. 静态成员变量   3. 静态成员函数   4. 虚函数   5. 纯虚函数

#影响对象大小的因素

0. 成员变量     1. 虚函数表指针(_vftptr)   2. 虚基类表指针(_vbtptr)   3. 内存对齐

_vftptr、_vbtptr的初始化由对象的构造函数, 赋值运算符自动完成;对象生命周期结束后,由对象的析构函数来销毁。
对象所关联的类型(type_info),通常放在virtual table的第一个slot中。

虚继承:在继承定义中包含了virtual关键字的继承关系;
虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:
class CDerive : public virtual CBase {}; 其中CBase称之为CDerive的虚基类,而不是说CBase就是个虚基类,因为CBase还可以为不是虚继承体系中的基类。

虚函数被派生后,仍然为虚函数,即使在派生类中省去virtual关键字。

注:【下文中_vbptr即_vbtptr】

#对象内存布局分类讨论

vc6变量查看器中(Locals,Watch1等),也可以看到部分对象布局的情况(不完整,且虚继承是错误的)。

vs2005及以后版本的编译器提供了/d1reportSingleClassLayout[类名]编译选项来查看对象完整的内存布局:

cl classLayout.cpp /d1reportSingleClassLayoutCChildren

0. 单一类

(1). 空类

sizeof(CNull)=1(用于标识该对象)

(2). 只有成员变量的类

int nVarSize = sizeof(CVariable) = 12

内存布局:

(3). 只有虚函数的类

int nVFuntionSize = sizeof(CVFuction) = 4(虚表指针)

内存布局:

(4). 有成员变量、虚函数的类

int nParentSize = sizeof(CParent) = 8

内存布局:

1. 单一继承(含成员变量、虚函数、虚函数覆盖)

int nChildSize = sizeof(CChildren) = 12

vc中显示的结果(注:还有1个虚函数CChildren::g1没有被显示出来):

d1reportSingleClass查看:

内存布局:

2. 多继承 (含成员变量、虚函数、虚函数覆盖)

int nChildSize = sizeof(CChildren) = 20

vc中显示的结果(注:还有2个虚函数CChildren::f2,CChildren::h2没有被显示出来,this指针的adjustor[调整值]也没打印出):

d1reportSingleClass查看:

内存布局:

3. 深度为2的继承(含成员变量、虚函数、虚函数覆盖)

int nGrandSize = sizeof(CGrandChildren) = 24

vc中显示的结果(注:还有3个虚函数CGrandChildren::f2,CChildren::h2,CGrandChildren::f3没有显示出来,this指针的adjustor[调整值]也没打印出):

d1reportSingleClass查看:

内存布局:

4 重复继承(含成员变量、虚函数、虚函数覆盖)

int nGrandSize = sizeof(CGrandChildren) = 28

vc中显示的结果(注:还有大量的虚函数没有显示出来,this指针的adjustor[调整值]也没打印出):

thunk函数:一种形实转换辅助函数;主要做this指针调整,函数调用重定向。

d1reportSingleClass查看:

内存布局:

由于m_nAge在内容中存在两个拷贝,因此我们不能直接通过pGrandChildrenA->m_nAge来访问该变量,

这样会存在二义性,编译器无法知道应该访问CChildren1中的m_nAge,还是CChildren2中的m_nAge。

为了标识唯一的m_nAge,就需要带上其所在范围的类名了。如下:

1 pGrandChildrenA->CChildren1::m_nAge = 1;
2 pGrandChildrenA->CChildren2::m_nAge = 2;

5. 单一虚继承(含成员变量、虚函数、虚函数覆盖)

int nChildSize = sizeof(CChildren) = 20

d1reportSingleClass查看:

内存布局:

6. 多虚继承(含成员变量、虚函数、虚函数覆盖)

(1) virtual CParent1, CParent2

int nChildSize = sizeof(CChildren) = 24

d1reportSingleClass查看:

内存布局:

(2) CParent1, virtual CParent2

int nChildSize = sizeof(CChildren) = 24

d1reportSingleClass查看:

内存布局:

(3) virtual CParent1, virtual CParent2

int nChildSize = sizeof(CChildren) = 28

d1reportSingleClass查看:

内存布局:

7. 钻石型的虚拟多重继承(含成员变量、虚函数、虚函数覆盖)

int nGrandChildSize = sizeof(CGrandChildren) = 36

d1reportSingleClass查看:

thunk函数:一种形实转换辅助函数;主要做this指针调整,函数调用重定向。

内存布局:

PS:虚基类表相对于虚函数表要稍微难理解些,故单独提出来。

  虚函数表是在对象生成时插入一个虚函数指针,指向虚函数表,这个表中所列就是虚函数。

  虚基类表原理与虚函数表类似,不过虚基类表的内容有所不同。表的第一项表示派生类对象指针相对于虚基类表指针的偏移,从第二项开始表示各个基类地址相对于虚基类表指针的偏移。

#外部参考

C++类对应的内存结构

陈皓- C++ 虚函数表解析

陈皓 - C++ 对象的内存布局(上)

陈皓 - C++ 对象的内存布局(下)

c++对象内存模型【内存布局】(转)的更多相关文章

  1. 转: 【Java并发编程】之十七:深入Java内存模型—内存操作规则总结

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17377197 主内存与工作内存 Java内存模型的主要目标是定义程序中各个变量的访问规则, ...

  2. 【Java并发编程】:深入Java内存模型—内存操作规则总结

    主内存与工作内存 java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.此处的变量主要是指共享变量,存在竞争问题的变量.Java内存模 ...

  3. JVM内存模型详解

    内存模型 内存模型如下图所示 堆 堆是Java虚拟机所管理的内存最大一块.堆是所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域唯一的目的就是存放对象实例.所有的对象实例都在这里分配内存 Ja ...

  4. Java 运行时数据区和内存模型

    运行时数据区是指对 JVM 运行过程中涉及到的内存根据功能.目的进行的划分,而内存模型可以理解为对内存进行存取操作的过程定义.总是有人望文生义的将前者描述为 "Java 内存模型" ...

  5. 【java多线程系列】java内存模型与指令重排序

    在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...

  6. 并发研究之Java内存模型(Java Memory Model)

    Java内存模型JMM java内存模型定义 上一遍文章我们讲到了CPU缓存一致性以及内存屏障问题.那么Java作为一个跨平台的语言,它的实现要面对不同的底层硬件系统,设计一个中间层模型来屏蔽底层的硬 ...

  7. Java内存模型及Java关键字 volatile的作用和使用说明

    先来看看这个关键字是什么意思:volatile  [ˈvɒlətaɪl] adj. 易变的,不稳定的; 从翻译上来看,volatile表示这个关键字是极易发生改变的.volatile是java语言中, ...

  8. 再有人问你Java内存模型是什么,就把这篇文章发给他

    前几天,发了一篇文章,介绍了一下JVM内存结构.Java内存模型以及Java对象模型之间的区别.有很多小伙伴反馈希望可以深入的讲解下每个知识点.Java内存模型,是这三个知识点当中最晦涩难懂的一个,而 ...

  9. 【深入理解JVM】:Java内存模型JMM

    多任务和高并发的内存交互 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per Second,TPS)这个指标 ...

  10. Java8内存模型

    一.JVM内存模型 内存空间(Runtime Data Area)中可以按照是否线程共享分为两块,线程共享的是方法区(Method Area)和堆(Heap),线程独享的是Java虚拟机栈(Java ...

随机推荐

  1. 0014.Linux环境搭建 Python环境搭建

    -安装Linux-- 找了了老男孩19期的运维班安装视频,尼玛真心不想看书,文字枯燥的要死,还不如直接看视频进行安装... 可怜了我的C盘只有1GB了...绝对不能安装在C盘...那就安装在E盘吧,足 ...

  2. 聊聊、Spring 第二篇

    之前写了一篇<Spring环境搭建一>,感觉写的很烂,也许是时间有限,写的很急.今天我想再写写 Spring 的环境搭建,因为 Spring 的模块是可以单独拿出来用的,所以有很多的模块不 ...

  3. WebService的简介, 原理, 使用,流程图

    WebService的简介, 原理, 使用   第一部分: 直观概述 WebService的几种概念: 以HTTP协议为基础,通过XML进行客户端和服务器端通信的框架/组件 两个关键点: 1.     ...

  4. js 抓取页面数据

    数据抓取 主要思路和原理 在根节点document中监听所有需要抓取的事件 在元素事件传递中,捕获阶段获取事件信息,进行埋点 通过getBoundingClientRect() 方法可获取元素的大小和 ...

  5. Android监听键盘右下角确定键

    代码改变世界 Android监听键盘右下角确定键 kotlin写法 <EditText android:id="@+id/seachFriend" android:layou ...

  6. Welcome-to-Swift-14构造过程(Initialization)

    构造过程是为了使用某个类.结构体或枚举类型的实例而进行的准备过程.这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务. 构造过程是通过定义构造器(Initializers)来实 ...

  7. [USACO08DEC] 秘密消息Secret Message (Trie树)

    题目链接 Solution Trie 树水题. 直接将前面所有字符串压入Trie 中. 在查询统计路上所有有单词的地方和最后一个地方以下的单词数即可. Code #include<bits/st ...

  8. APUE 学习笔记(七) 信号

    1.信号是软件中断,提供一种异步处理事件的方法 很多事件产生信号: (1)用户按下某些中断键,如 Ctrl + C键产生 SIGINT信号 (2)硬件异常产生信号,比如 除数为0,无效的内存引用  ( ...

  9. SqlServer-1

    参考:https://blog.csdn.net/qq_29413829/article/details/80077550 安装sqlServer2012:https://blog.csdn.net/ ...

  10. AxureRP8 实现时间功能

    利用AxureRP8中空间的动态面板的状态改变时间设置文本的值,从而实现时间功能,如下内容. 1.新建index页面,如已有index页面忽略这步即可. 2.拖入一个文本标签,将文本标签的名称命名为: ...