当前位置: 首页 > 编程日记 > 正文

C++ 类的内存分布

C++类内存分布

转自:http://www.cnblogs.com/jerry19880126/p/3616999.html
先写下总结,通过总结下面的例子,你就会明白总结了。

下面总结一下:

1、虚基类指针和虚函数指针是可以继承的

2. 虚函数指针来源于父类或者自己是第一个声明虚函数的类(前提是自己没有继承虚函数指针,如果继承了,就不会产生新的虚函数指针),那么子类将父类的虚函数指针继承下来,并指向自身的虚表(发生在对象构造时,自己新建的表,不是父类的)。有多少个虚函数,虚函数表里面的项就会有多少。多重继承时,可能存在多个的基类虚函数表与虚函数指针;

3、如果是多重继承的情况下,子类中含有多个来自父类的虚函数表,子类新写了一个虚函数,那么该虚函数的地址,只会添加在第一个继承的父类的虚函数指针指向的表的尾部。如果重写了父类的虚函数,那么含有该虚函数地址的多个虚函数表都会更改地址。

(上面图中和真实的内存情况不对应,虚函数指针不是这么紧密排列在一起的,但是原理是对的)

4. 如果是虚继承,那么子类就会产生(自己的)虚基类指针指向其虚基类表(多重虚继承的话,也只产生一个),虚基类表包括其继承体系上的所有被虚继承的类。(当然其内存中也会继承其父类的虚基类指针和虚基类表)

书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别是在继承、虚函数存在的情况下。

工欲善其事,必先利其器,我们先用好Visual Studio工具,像下面这样一步一步来:

先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。近期的VS版本都支持这样配置。

下面可以定义一个类,像下面这样:

复制代码
1 class Base
2 {
3     int a;
4     int b;
5 public:
6     void CommonFunction();
7 };
复制代码

然后编译一下,可以看到输出框里面有这样的排布:

这里不想花精力在内存对齐因素上,所以成员变量都设为int型。

从这里可以看到普通类的排布方式,成员变量依据声明的顺序进行排列(类内偏移为0开始),成员函数不占内存空间。

再看下继承,往后面添加如下代码:

复制代码
1 class DerivedClass: public Base
2 {
3     int c;
4 public:
5     void DerivedCommonFunction();
6 };
复制代码

编译,然后看到如下的内存分布(父类的内存分布不变,这里只讨论子类成员变量的内存分布):

可以看到子类继承了父类的成员变量,在内存排布上,先是排布了父类的成员变量,接着排布子类的成员变量,同样,成员函数不占字节。

下面给基类加上虚函数,暂时注释掉DerivedClass,看一下这时的内存排布:

复制代码
1 class Base
2 {
3     int a;
4     int b;
5 public:
6     void CommonFunction();
7     void virtual VirtualFunction();
8 };
复制代码

这个内存结构图分成了两个部分,上面是内存分布,下面是虚表,我们逐个看。VS所带编译器是把虚表指针放在了内存的开始处(0地址偏移),然后再是成员变量;下面生成了虚表,紧跟在&Base1_meta后面的0表示,这张虚表对应的虚指针在内存中的分布,下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数列出来。

编译器是在构造函数创建这个虚表指针以及虚表的。

那么编译器是如何利用虚表指针与虚表来实现多态的呢?是这样的,当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。

所以,如果是调用Base *p = new Derived();生成的是子类的对象,在构造时,子类对象的虚指针指向的是子类的虚表,接着由Derived*到Base*的转换并没有改变虚表指针,所以这时候p->VirtualFunction,实际上是p->vfptr->VirtualFunction,它在构造的时候就已经指向了子类的VirtualFunction,所以调用的是子类的虚函数,这就是多态了。

下面加上子类,并在子类中添加虚函数,像下面这样:

复制代码
1 class DerivedClass: public Base
2 {
3     int c;
4 public:
5     void DerivedCommonFunction();
6     void virtual VirtualFunction();
7 };
复制代码

可以看到子类内存的排布如下:

上半部是内存分布,可以看到,虚表指针被继承了,且仍位于内存排布的起始处,下面是父类的成员变量a和b,最后是子类的成员变量c,注意虚表指针只有一个,子类并没有再生成虚表指针了;下半部的虚表情况与父类是一样的。

我们把子类换个代码,像这样:

复制代码
1 class DerivedClass1 : public Base
2 {
3     int c;
4 public:
5     void DerivedCommonFunction();
6     void virtual VirtualFunction2();
7 };
复制代码

注意到这时我们并没有覆写父类的虚方法,而是重声明了一个新的子类虚方法,内存分布如下:

还是只有一个虚表指针,但是下方虚表的内容变化了,虚表的0号是父类的VirtualFunction,而1号放的是子类的VirtualFunction2。也就是说,如果定义了DerivedClass的对象,那么在构造时,虚表指针就会指向这个虚表,以后如果调用的是VirtualFunction,那么会从父类中寻找对应的虚函数,如果调用的是VirtualFunction2,那么会从子类中寻找对应的虚函数。

我们再改造一下子类,像这样:

复制代码
1 class DerivedClass1 : public Base
2 {
3     int c;
4 public:
5     void DerivedCommonFunction();
6     void virtual VirtualFunction();
7     void virtual VirtualFunction2();
8 };
复制代码

我们既覆写父类的虚函数,也有新添的虚函数,那么可以料想的到,是下面的这种内存分布:

下面来讨论多重继承,代码如下:

复制代码
 1 class Base2 {3     int a;4     int b;5 public:6     void CommonFunction();7     void virtual VirtualFunction();8 };9 
10 
11 class DerivedClass1: public Base
12 {
13     int c;
14 public:
15     void DerivedCommonFunction();
16     void virtual VirtualFunction();
17 };
18 
19 class DerivedClass2 : public Base
20 {
21     int d;
22 public:
23     void DerivedCommonFunction();
24     void virtual VirtualFunction();
25 };
26 
27 class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
28 {
29     int e;
30 public:
31     void DerivedDerivedCommonFunction();
32     void virtual VirtualFunction();
33 };
复制代码

内存分布从父类到子类,依次如下:

Base中有一个虚表指针,地址偏移为0

DerivedClass1继承了Base,内存排布是先父类后子类。

DerivedClass2的情况是类似于DerivedClass1的。

下面我们重点看看这个类DerivedDerivedClass,由外向内看,它并列地排布着继承而来的两个父类DerivedClass1与DerivedClass2,还有自身的成员变量e。DerivedClass1包含了它的成员变量c,以及Base,Base有一个0地址偏移的虚表指针,然后是成员变量a和b;DerivedClass2的内存排布类似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base。

这里有两份虚表了,分别针对DerivedClass1与DerivedClass2,在&DerivedDericedClass_meta下方的数字是首地址偏移量,靠下面的虚表的那个-16表示指向这个虚表的虚指针的内存偏移,这正是DerivedClass2中的{vfptr}在DerivedDerivedClass的内存偏移。

如果采用虚继承,像下面这样:

复制代码
 1 class DerivedClass1: virtual public Base2 {3     int c;4 public:5     void DerivedCommonFunction();6     void virtual VirtualFunction();7 };8 9 class DerivedClass2 : virtual public Base
10 {
11     int d;
12 public:
13     void DerivedCommonFunction();
14     void virtual VirtualFunction();
15 };
16 
17 class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2
18 {
19     int e;
20 public:
21     void DerivedDerivedCommonFunction();
22     void virtual VirtualFunction();
23 };
复制代码

Base类没有变化,但往下看:

DerivedClass1就已经有变化了,原来是先排虚表指针与Base成员变量,vfptr位于0地址偏移处;但现在有两个虚表指针了,一个是vbptr,另一个是vfptr。vbptr是这个DerivedClass1对应的虚表指针,它指向DerivedClass1的虚表vbtable,另一个vfptr是虚基类表对应的虚指针,它指向vftable。

下面列出了两张虚表,第一张表是vbptr指向的表,8表示{vbptr}与{vfptr}的偏移;第二张表是vfptr指向的表,-8指明了这张表所对应的虚指针位于内存的偏移量。

DerivedClass2的内存分布类似于DerivedClass1,同样会有两个虚指针,分别指向两张虚表(第二张是虚基类表)。

下面来仔细看一下DerivedDerivedClass的内存分布,这里面有三个虚指针了,但base却只有一份。第一张虚表是内含DerivedClass1的,20表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离,第二张虚表是内含DerivedClass2的,12表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离,最后一张表是虚基表,-20指明了它对应的虚指针{vfptr}在内存中的偏移。

虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(更多的虚表指针)。

当如下情况时:

class A
{
public:int a;
};
class B:virtual public A
{
public:int b;};
class C:virtual  public B
{
public:int c;};

内存结构如下:

如图所示,存在两个虚基表,一个是属于C本身的其中也包含了A和B的,一个是其父类B 的,只含有A的

在看一个例子

class A
{
public:int a;
};
class B:virtual public A
{
public:int b;};
class C:virtual public B
{
public:int c;};class D: virtual public B
{
public:int d;
};
class E:public C,public D
{
public:int e;
};

对于多重虚继承的情况

 1 class A
 2 {
 3 
 4 public:
 5 
 6     int a;
 7 
 8 };
 9 
10 class B:virtual public A
11 
12 {
13 
14 public:
15 
16     int b;
17 
18  
19 
20 };
21 
22 class C:virtual public A
23 
24 {
25 
26 public:
27 
28     int c;
29 
30  
31 
32 };
33 
34  
35 
36 class D: virtual public B,virtual public C
37 
38 {
39 
40 public:
41 
42     int d;
43 
44 };

上面的情况说明了,多重虚继承也只产生一个虚基类指针,和一个虚基类表,虚基类表包括了继承体系中所有被虚继承的类。

转载于:https://www.cnblogs.com/acSzz/p/5664922.html

相关文章:

iOS 关于手机权限的检查与获取

手机通讯录权限: /** * 检测权限并作响应的操作 */ - (void)checkAuthorizationStatus:(UISwitch *)sender { switch (ABAddressBookGetAuthorizationStatus()) { case kABAuthorizationStatusAuthorized: //存在权限 //获取通讯…

也谈谈区块链技术

链客,专为开发者而生,有问必答! 此文章来自区块链社区,未经允许拒绝转载。 现在区块链技术很火,而且几乎被上升到了一个“革命性”的高度,很多股票居然都因为沾了点区块链变得炙手可热。其实这玩意没有这么…

nyoj——297(期望)

GoroSort 时间限制:3000 ms | 内存限制:65535 KB难度:4描述Goro has 4 arms. Goro is very strong. You dont mess with Goro. Goro needs to sort an array of N different integers. Algorithms are not Goros strength; strength is Gor…

js ajax调用请求

<pre name"code" class"html"> function getAppList(env){var data {};data.env env;var successfn function(jdata){$(".deploy_list").html("");_HTML "<tr><th>发布清单</th></tr>";$…

iOS SDWebImage加载webp

项目更新使用的最新版本的SDWebImage, 需配置如下: Build Settings -> preprocessor macros -> 添加 SD_WEBP1

区块链之初识区块链

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自链客区块链技术问答社区&#xff0c;未经允许拒绝转载。 首先得明白几个概念&#xff1a;区块链&#xff0c;比特币&#xff0c;中心化&#xff0c;去中心化&#xff0c;挖矿 区块链和比特币 区…

Linux RSS/RPS/RFS/XPS对比

RSS适合于多队列网卡&#xff0c;把不同的流分散的不同的网卡多列中&#xff0c;至于网卡队列由哪个cpu处理还需要绑定网卡队列中断与cpuRPS&#xff1a;适合于单队列网卡或者虚拟网卡&#xff0c;把该网卡上的数据流让多个cpu处理RFS&#xff1a;当流量需要传输到用户态处理时…

iOS 关于UIView覆盖StatusBar的小知识点

项目中有关于浏览图片的需求, 自己写了一套, 但是一直有个关于StatusBar的问题: 因为在查看图片时隐藏掉了StatusBar, 当结束查看后再显示sta会发现整个界面下滑了20px, 在IM聊天界面这个滑动效果很不友好 最近在优化这一块东西时又想到了这个问题, 现在得到了比较好的解决方…

从数字货币说起

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自链客区块链技术问答社区&#xff0c;未经允许拒绝转载。 从数字货币说起 历史上&#xff0c;货币的形态经历了多个阶段的演化&#xff0c;包括实物货币、金属货币、代用货币、信用货币、电子货币、…

git常用命令及规范流程

参考地址&#xff1a;https://www.cnblogs.com/my--sunshine/p/7093412.html&#xff0c;感谢分享 官网地址&#xff1a;https://git-scm.com/book/zh/v2 git init 在本地新建一个repo,进入一个项目目录,执行git init,会初始化一个repo,并在当前文件夹下创建一个.git文件夹.git…

关于iOS 11的适配

距离iOS 11正式发布也有小半年了, 陆陆续续也看到许多关于iOS 11和iPhone X适配相关的文章, 现记录下自己做适配所做的工作 首先给出自己适配所用到的宏定义, 如下://状态栏 #define kStatusBarHeight [[UIApplication sharedApplication] statusBarFrame].size.height //导航条…

PHP实现队列的原理

关于的队列的介绍&#xff0c;我这里就不多讲了&#xff0c;随便百度一下都很多 用过laravel框架的童鞋都知道其自带队列功能&#xff0c;之前我很费解&#xff0c;PHP只是一个脚本&#xff0c;有超时机制 为什么能不停的去执行队列呢&#xff1f; 带着这个问题&#xff0c;在网…

实现代币的管理者

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 实现代币的管理者 虽然区块链是去中心化的&#xff0c;但是实现对代币&#xff08;合约&#xff09;的管理&#xff0c;也在许多应用中有需求&…

Oracle数据库基本操作(二) —— 视图、序列、索引、同义词

一、视图&#xff08;Views&#xff09;与 同义词 1、视图:实际上是对查询结果集的封装,视图本身不存储任何数据,所有的数据都存放在原来的表中; 在逻辑上可以把视图看作是一张表 2、作用: 封装查询语句,简化复杂的查询需求屏蔽表中的细节3、语法: create [or replace] view 视…

iOS crash日志分析

项目集成talkingdata收集到的crash日志, 看到那些日志时自己也是很崩溃, 全是内存地址, 根本搞不懂项目到底crash到了那里, 比如这样:自己在网上找了很多方法, 以下是自己最后所用到的方法(心累): 1, 首先拿到.dSYM 文件, 步骤:XCode中的Window -> Organizer -> 找到App …

Xamarin Android项目运行失败

Xamarin Android项目运行失败 错误信息&#xff1a;Build Failed: MonoDroid does not support running the previous version. Please ensure your solution builds before running or debugging it.这是由于由于项目生成失败&#xff0c;并找不到以前编译的结果。这时&#…

logging模块

import logging from conf import settingsdef logger(log_type):# 生成 logger 对象logger logging.getLogger(log_type)logger.setLevel(settings.LOG_LEVEL)# 生成handler对象&#xff0c;向文件输出日志信息log_file "%s/log/%s.log" % (settings.BASE_DIR, lo…

iOS 模糊效果相关

项目中一直有使用到模糊处理, 例如图片的高斯模糊, 一直使用的代码如下: // 内部方法,核心代码,封装了毛玻璃效果 参数:半径,颜色,色彩饱和度 - (UIImage *)imgBluredWithRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturati…

CDN的原理及对SEO的影响

http://www.williamlong.info/archives/4059.html CDN的概念最早于1995年由美国麻省理工大学提出&#xff0c;是一套能够实现用户就近访问的网络解决方案。具体方法是&#xff1a;采用智能路由和流量管理技术&#xff0c;将用户的访问请求指向 CDN网络中健康且响应最快的CDN节点…

JavaScript学习笔记 - 入门篇(1)- 准备

为什么学习JavaScript 一、你知道&#xff0c;为什么JavaScript非常值得我们学习吗&#xff1f; 所有主流浏览器都支持JavaScript。目前&#xff0c;全世界大部分网页都使用JavaScript。它可以让网页呈现各种动态效果。做为一个Web开发师&#xff0c;如果你想提供漂亮的网页、令…

iOS关于像素的适配

项目中很多地方会用到分割线, 一般设置为1.0, 但是在不同的机型上这个1.0显示的效果是不同的,1x的机型上正常, 2x和3x的机型上显示的就会很粗, 影响适配, 困扰很久后得到了以下解决办法, 在此记录一下: 1.0/UIScreen.mainScreen.scale

非对称加密中公钥

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 数字签名是公钥密码体系中签名验证功能的一个应用。其目的是保证信息传输的完整性、发送者的身份认证、防止交易中的抵赖发生。其中数字签名是个加密…

bzoj 4813: [Cqoi2017]小Q的棋盘【树形dp】

这么简单的dp我怎么没想到x2 f为从这个点出发后回到这个点最多能走过的点&#xff0c;g为从这个点出发后不回到这个点最多能走过的点&#xff0c;注意g有两种转移&#xff1a;g[u][k]max(g[u][k],f[u][k-j-1]g[e[i].to][j])是在e[i].to这个子树前走了一棵子树再回来&#xff0c…

spring WebServiceTemplate 调用 axis1.4 发布的webservice

前言&#xff1a; 最近在开发中需要调用对方的 webservice服务&#xff0c;按照现有的技术&#xff0c;本应该是一件很简单的事情&#xff0c;只需要拿到wsdl文件&#xff0c;生成客户端代码即可&#xff0c;但是&#xff0c;对方的webservice服务是06年用axis1.4生成发布的&am…

iOS 获取Assets中的启动页

app启动时先进入一个广告页, 若无广告图则用启动页占位, 一直为这个占位图的适配烦恼, 最近查资料终于找到了结果, 现记录一下: - (UIImage *)getLaunchImage { CGSize viewSize [UIScreen mainScreen].bounds.size; NSString *viewOrientation "Portrait";//方向…

SunlightChain 区块链宣言

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 SunlightChain 区块链宣言 区块链技术的应用必将颠覆现在过度依赖于中心的经济模式&#xff0c;它与生俱来的开放、共享、去中心化等特点极大地提高…

Ajax跨域:Jsonp原理解析

推荐先看下这篇文章&#xff1a;JS跨域&#xff08;ajax跨域、iframe跨域&#xff09;解决方法及原理详解&#xff08;jsonp&#xff09; JavaScript是一种在Web开发中经常使用的前端动态脚本技术。在JavaScript中&#xff0c;有一个很重要的安全性限制&#xff0c;被称为“Sam…

iOS 微信SDK1.8.6后需要UniversalLink解决方案及采坑记录

项目最初因审核原因,一直使用iOS原生分享, 最近因项目需求要求, 接入微信分享, 以为和原来的没有区别, 但是接入时才发现改动的地方还是挺多的, 主要是需要配置UniversalLink和提包时的一些问题, 在此做一下记录 UniversalLink配置步骤 1.制作apple-app-site-association文件…

GO语言编程基础-复合类型结构体

链客&#xff0c;专为开发者而生&#xff0c;有问必答&#xff01; 此文章来自区块链技术社区&#xff0c;未经允许拒绝转载。 1 结构体类型 有时我们需要将不同类型的数据组合成一个有机的整体&#xff0c;如&#xff1a;一个学生有学号/姓名/性别/年龄/地址等属性。显然单独…

【BZOJ1015】【JSOI2008】星球大战 并查集

题目大意 给你一张\(n\)个点\(m\)条边的无向图&#xff0c;有\(q\)次操作&#xff0c;每次删掉一个点以及和这个点相邻的边&#xff0c;求最开始和每次删完点后的连通块个数。 \(q\leq n\leq 400000,m\leq 200000\) 题解 我们可以用并查集维护连通块个数&#xff0c;可惜并查集…