C++对象的内存分析(精)

C++对象模型之简述C++对象的内存布局 在C++中,有两种类的成员变量:static和非static,有三种成员函数:static、非static和virtual。那么,它们如何影响C++的对象在内存中的分布呢? 当存在继承的情况下,其内存分布又是如何呢? 下面就一个非常简单的类,通过逐渐向其中加入各种成员,来逐一分析上述两种成员变量及三种成员函数对类的对象的内存分布的影响。 注:以下的代码的测试结果均是基于Ubuntu 14.0 阅读详情

http://www.cnblogs.com/Binhua-Liu/archive/2010/06/16/1759019.html点击打开链接

document 介绍

    虚函数表、虚指针、多态、重写(override)、虚析构、指针调整… 这些概念大家应该都不陌生,不过,除了了解概念和用法,你了解他们背后的实现的机制吗。 本文通过的C++类的对象内存进行分析,来讲解这些面向对象的特性是怎么实现的。本文的目的是为了更好的理解C++面向对象的特性的实质,而对于编译器实现的细节并不会过多的涉及。本文所指的C++,在没有特别说明时,特指VC++。

    本文将首先将进行4个课题的研究,分别分析4个C++类的对象内存,最后,讲解构造函数,析构函数和虚析构函数的原理。将分为以下几个章节:

  1. Subject1:一个带虚函数的基类
  2. Subject2:从带虚函数的基类继承的子类
  3. Subject3:从不带虚函数的基类继承的子类
  4. Subject4:多重继承
  5. 构造函数与析构函数

 

documentSubject1:一个带虚函数的基类。

    首先我们来分析一个带虚函数的基类对象的内存布局,厘清一些基本的问题,对于我们接下来分析继承以及多重继承的情况,是很有帮助的。

    下面是我们要分析的CBasic类:

image

代码如下:

class  CBasic
{
   public :
       CBasic()
       {
           Array= new  int [2];
       }
       int  i;
       int  *Array;
       virtual  int  add( int  a, int  b)
       {
           return  a+b;
       }
       virtual   int  minus( int  a, int  b)
       {
           return  a-b;
       }
      void  HelloWorld()
      {
          cout<< "hello world" <<endl;
      }
};

    并且在main函数中构造这个类的一个对象:

CBasic *b= new  CBasic;

 

    分析该对象内存的最好工具是Visual Studio的Watch窗口。按F5进行调试,然后点击主菜单的Debug->Windows->Watch->Watch1,在Watch窗口中输入以下元素并观察:

image

 

 

    仔细观察Watch1表中元素地址我们发现:b对象在内存中占12个字节;b对象的第一个元素为4字节长的虚函数指针(因此,虚函数指针地址和对象地址相同),第2个元素为4字节的整型i,第3个元素为4字节的整型指针Array。

    我们还发现:虚函数指针指向一个虚函数表(虚函数表并不在b对象内存地址内),虚函数表是一个函数指针数组,第一个元素指向虚函数add,第二个元素指向虚函数minus。

    我们可以画出b对象的内存结构图了:

image

 

    很显然,在调用b对象的2个虚函数add和minus时,是通过虚函数指针找到虚函数表,再从虚函数表索引到函数的地址的。但是,下面2个问题我们还没有结论:

    1)普通成员函数HelloWorld是怎么被调用的呢?在对象的内存和虚表中,我们没有看到任何关于它的信息。

    2)CBasic类每个对象都有一个独有的虚函数表吗?还是所有对象共有一个呢?

    首先来看第一个问题,看下面的代码:

CBasic *b= new  CBasic;
  b->HelloWorld();
  b->add(1,2);
  b->minus(3,4);

    下面我们查看这段代码的汇编:按F5调试,鼠标放在以上代码处,点击右键,选择Go to disassembly,我们可以看到以下片段

      b->HelloWorld();
012146F6  mov         ecx,dword ptr [ebp-14h]
012146F9  call        CBasic::HelloWorld (12112B2h)

    从汇编代码我们看出:对普通成员函数HelloWord的调用在编译时转换为对函数地址的直接调用了。因此,在对象内存中不需要保持关于它的地址信息。
    这和虚函数的调用方式是完全不同的,下面的汇编代码片段再次证明了虚函数是通过虚函数指针和虚函数表来调用的:

      b->add(1,2);
012146FE  mov         esi,esp
01214700  push        2    
01214702  push        1   
01214704  mov         eax,dword ptr [ebp-14h]
01214707  mov         edx,dword ptr [eax]
01214709  mov         ecx,dword ptr [ebp-14h]
0121470C  mov         eax,dword ptr [edx]
0121470E  call        eax 

 

      b->minus(3,4);
01214717  mov         esi,esp
01214719  push        4   
0121471B  push        3   
0121471D  mov         eax,dword ptr [ebp-14h]
01214720  mov         edx,dword ptr [eax]
01214722  mov         ecx,dword ptr [ebp-14h]
01214725  mov         eax,dword ptr [edx+4]
01214728  call        eax 

    因此,上面的调用可以认为被编译器翻译成了如下代码

CBasic *b= new  CBasic;
  b->12112B2h();
  b->__vfptr[0](1,2);
  b->__vfptr[1](3,4);

    我们来看第二个问题,构造2个CBasic对象:b和b1,在wacth窗口中观察b->__vfptr和b1->__vfptr的值,它们是相同的,说明它们指向同一个虚表。很显然,类所有对象使用的虚表的结构都是相同的,编译器没有必要为每个对象都独立地分配一个虚表,而是让他们公用一个。

documentSUBJECT1:总结

    让我们对一个带虚函数的类的内存布局做一个总结:

    1)虚函数指针存储在对象内存的最开始4个字节中(这是C++规范决定的)【1】,虚函数指针指向虚表地址。

    2)虚函数表是存储函数指针的数组,按照虚函数定义的顺序存储了所有虚函数的实际地址。虚函数被调用时,程序通过虚函数指针索引到虚函数表,再通过虚函数表索引到虚函数的实际地址。虚表并不是对象内存的一部分,类的所有对象共有一个虚函数表【2】.

    3)对普通成员函数的调用,编译时就直接编译成对函数地址的直接调用。

    4)‘值类型’数据直接存储在对象的内存中;’引用类型’(对象,堆上分配的数组等)只有指向其实际地址的指针存储在对象内存中。

 

document注释

[1]在没有继承或者单继承的情况下,虚函数指针只有一个且始终处于最前端。在多重继承下,可能有好几个虚函数指针,主基类的虚函数指针位于最前端。

[2]虚函数表是类关联信息的一部分。

 

document授权声明

    本文为Binhua Liu原创作品。本文允许复制,修改,传递。转载请注明出处。本文发表于2010年6月16日。


C++对象内存模型 C语言中数据和处理数据的函数是分别定义,各个处理数据的函数实现相应各种算法。但是C++提供了类,可以实现较好的数据和处理数据的算法的封装性,这种封装性相比较C语言而言会带来一些成本,这主要受制于是C++对象为支持相应特性而实现的内存模型。 C++中分别有static和nonstatic两种数据成员,有static、nonstatic、virtual三种成员函数。对于一个类的对象的内存布局方式主要经 阅读详情

相关推荐

C++笔记之sizeof()判断对象所占内存空间的大小以及字符数组的长度

【代码】C++笔记之sizeof()判断对象所占内存空间的大小以及字符数组的长度。

小勇博客 538

C++对象的内存分析(1)

介绍 虚函数表、虚指针、多态、重写(override)、虚析构、指针调整… 这些概念大家应该都不陌生,不过,除了了解概念和用法,你了解他们背后的实现的机制吗。 本文通过的C++类的对象内存进行分析,来讲解这些面向对象的特性是怎么实现的。本文的目的是为了更好的理解C++面向对象的特性的实质,而对于编译器实 现的细节并不会过多的涉及。本文所指的C++,在没有特别说明...

weixin_33860553的博客 217

别再手动抄表了!用PaddleOCR超轻量模型5分钟搞定数字仪表识别(附完整Python代码)

本文介绍如何利用PaddleOCR超轻量模型快速实现工业仪表数字识别自动化。通过5行核心代码和关键预处理步骤,显著提升识别准确率至98.2%,解决传统OCR在工业场景中的体积大、抗干扰弱等问题,并提供完整Python代码和边缘部署优化方案。

weixin_31062533的博客 349

C++对象模型笔记:对象实例内存布局的小小结

前一篇笔记中已经说出了类的对象实例在内存中的三种可能的内存布局,其中,最后一种是c++编译器正在使用的。这种布局的特点如下(先抛开虚函数方面的信息,说一般的) 1、非静态(non-static)数据成员,每个对象实例的内存空间里都有一份; 2、静态(static)数据成员,静态和非静态的成员函数:整个内存空间里面仅有一份(对象实例的内存空间是没有这方面的信息的) 原来,

jiangnanyouzi的专栏 1590

5G NR上行控制信道PUCCH

一、PUCCH概述 PUCCH用于承载上行控制信息,相比LTE,NR PUCCH支持5种不同的格式,按照时域上所占用的符号数量可以分为短格式和长格式两种,如下表所示,短格式占用1-2个符号,可以承载1-2bit信息,长格式占用4-14个符号,可承载大于2bit的信息。NR引入短格式PUCCH的目的是可以缩短HARQ-ACK反馈的时延,长格式仍然是考虑到持续时间长可以保证覆盖。 NR中,考虑到系...

一个关于5G NR的博客^^ 3万+

uboot引导vxworks6.9(T4240) 启动

vxworks内核配置下搜索RAM_LOW_ADRS, 值为0x01000000, 是vxworks下载启动的地址,uboot启动时会用到。vxworks6.9 t4240用的是32地址空间,uboot下用的是64位地址空间,去uboot下修改law物理地址空间为32位。vxworks下flash基地址为 0xff000000, 将vx下的地址修改为0xe0000000,和uboot下地址一致。uboot下flash基地址为0xe000000,vxworks内核配置为build-in 符号表。

珂珂可爱多 3236

C++中类对象所占空间的大小

C++中类对象所占空间的大小 http://www.cnblogs.com/easonpan/archive/2012/04/25/2470177.html   首先,平时所声明的类只是一种类型定义,它本身是没有大小可言的。 因此,如果用sizeof运算符对一个类型名操作,那得到的是具有该类型实体的大小。    计算一个类对象的大小时的规律: 空类、单一继承的空类

anhkgg的专栏 7801

信捷PLC XD5系列运动控制(10轴)编程应用篇

项目PLC采用XD5-60T10-E,36DI 24DO 晶体管输出,支持10路脉冲输出功能。TMR指令定时器时基可以自己设定,这点比其他品牌PLC方便。ZRN指令我们需要上升沿指令触发。 5、变址寻址 该说不说这个变址寻址是真的强,比西门子指针、三菱的变址寄存器Z 、V都要更加灵活。 7、特殊寄存器:时钟 8、如何建立PLC通讯...............

RXXW_Dor的博客 1万+

【C++】使用sizeof计算类对象所占空间大小-sizeof总结

 决定C ++中对象的大小的因素: 1.所有非静态数据成员的大小 2.数据成员的顺序 3.字节对齐或字节填充 4.其直接基类的大小虚函数的存在 5.  正在使用的编译器 6.继承模式(虚拟继承)   一、使用sizeof计算类对象所占空间大小   需要注意,对类做sizeof运算时,并不是简单地把各个成员所占的内存数量相加。需要注意成员顺序不同,可能需要字节补齐。 ...

渲染引擎开发 8579

GEE:面对对象土地利用分类(结合SNIC、GLCM 和机器学习算法)

本文将介绍在Google Earth Engine中结合SNIC、GLCM和机器学习算法的的土地利用/土地覆盖(LULC)分类方法。结果如下图所示,

养乐多的博客 5614

GPS系统跟踪捕获算法的Verilog实现在FPGA

本文将介绍GPS系统中的跟踪捕获算法,并提供该算法的Verilog实现在FPGA上运行的源代码。常用的方法是将接收到的信号与一个本地振荡器产生的参考信号进行混频,然后使用带通滤波器提取出希望得到的频率分量。码跟踪的目的是通过检测接收到的信号中的码片序列来实现对导航数据的解调。以上是一个简化的GPS系统跟踪捕获算法的Verilog实现,其中包含了时间检测、频率跟踪和码跟踪三个模块。GPS信号由载波频率和导航数据组成。跟踪捕获算法的目标是通过对接收到的信号进行处理,从而获取到准确的载波频率和导航数据。

PixelEnigma的博客 1108

C++对象内存分析

一、问题的引出 对C++模型的认识可以从本质上提高对语言和各种机制的理解,如果对底层机制一无所知,那么很多高级的机制都只能通过死记硬背的方式来运用,而且有时候有错误,也很难找出原因。C++相对与C语言,编译器做了很多的对程序员透明的事情,而很多错误也是由于这些不透明所引起的。所以研究这个问题是很有必要的。   二、引起问题的原因 或者说是使问题复杂的原因,那些使得内存分配超乎想象的原因。大

胜利者的经验并不复杂,那就是寻找人生的关键点,并在关键时刻竭尽全力 3109

1.DJI-payload SDK负载开发准备和硬件连接及运行示例代码

大疆psdk开发环境

哒宰的博客 1442

C++对象模型之详述C++对象的内存布局

本文主要讨论继承对于对象的内存分布的影响,包括:继承后类的对象的成员的布局、继承对于虚函数表的影响、virtual函数机制如何实现、运行时类型识别等。由于在C++中继承的关系比较复杂,所以本文会讨论如下的继承情况: 1)单一继承 2)多重继承 3)重复继承 4)单一虚拟继承 5)钻石型虚拟继承

ljianhui的专栏 1万+

手把手教你学Simulink--基于飞行控制与稳定性的场景实例:固定翼无人机的自动起飞与降落控制

本文介绍了使用Simulink对固定翼无人机自动起飞与降落控制系统进行建模与仿真的完整流程。首先构建了包含飞行器动力学模型、控制系统、环境模拟等模块的系统结构,详细阐述了简化动力学方程的推导和实现。重点讲解了PID控制器设计,包括起飞阶段的速度控制和降落阶段的高度控制策略。通过添加环境干扰和输入信号,建立了完整的仿真模型,并利用输出显示模块评估系统性能。该实例为无人机自主控制系统的开发提供了有效的仿真验证方法,有助于优化参数提高飞行稳定性。

MHD0815的博客 4309

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

C++类对象在内存中的布局

目录 一、前言 二、C++ 类对象的内存布局 2.1 只有数据成员的对象 2.2 没有虚函数的对象 2.3 拥有仅一个虚函数的类对象 2.4 拥有多个虚函数的类对象 三、继承关系中的C++类对象内存分布 3.1 存在继承关系且本身不存在虚函数的派生类的内存布局 ​3.2本身不存在虚函数(不严谨)但存在基类虚函数覆盖的单继承类的内存布局 3.3 定义了基类没有的虚函数的单继承的类对象布局 3.4 多继承且存在虚函数覆盖同时又存在自身定义的虚函数的类对象布局 3.5如果第1个直接基...

qq_41687938的博客 3601

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

C++类对象的内存布局

1、C++类对象的内存布局 在C++的类对象中,有两种类的成员变量:static和非static,有三种成员函数:static、非static和virtual。那么,它们在C++的内存中是如何分布的呢? C++程序的内存格局通常分为四个区:全局数据区(data area),代码区(code area),栈区(stack area),堆区(heap area)(即自由存储区...

好记性不如写博客! 8993

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

C++对象的内存分析

http://www.cnblogs.com/Binhua-Liu/archive/2010/06/16/1759019.html  介绍     虚函数表、虚指针、多态、重写(override)、虚析构、指针调整… 这些概念大家应该都不陌生,不过,除了了解概念和用法,你了解他们背后的实现的机制吗。 本文通过的C++类的对象内存进行分析,来讲解这些面向对象的特性是怎么实

lx_shudong的专栏 448

C++ 对象内存排布

C++ 对象内存排布复习

C/C++ 1059

C++ 对象内存布局

可能会对 C++ 对象的内存布局产生影响的因素: 对象的数据成员变量 对象的一般成员函数 对象的虚成员函数 对象继承 - 父类不包含虚函数 对象继承 - 父类包含虚函数 对象多继承 - 父类不包含虚函数 对象多继承 - 父类包含虚函数 对象虚继承 动态类型信息(这里不讨论) 通过汇编代码看虚函数调用过程 这些特性的实现常常因操作系统平台和编译器/ABI 而异,这里的分析主要基于 linux x86_64 平台上的 GCC 11.2.0/G++ 11.2.0 的行为进行。 一般 C++ 对象的内存布局

tq08g2z的专栏 1458

深度解读《深度探索C++对象模型》之C++对象的内存布局(一)

C++语言相比C语言最重要的功能就是支持面向对象编程,为了实现面向对象编程,C++增加了类的封装和多态、继承等特性,那么这些特性的加入是否会造成对象的内存成本增加?如果增加了,那么到底增加了多少?

iShare_Carlos的博客 1735

object-introspection:实时C++对象内存分析

object-introspection:实时C++对象内存分析 项目介绍 object-introspection 是一种面向 C++ 对象的内存分析技术,它提供了一种动态工具,能够在不修改代码或重新编译的情况下,实时捕获整个对象层次结构(包括所有容器和动态分配)的确切内存占用情况。这项技术的出现,为开发者提供了一种高效、便捷的内存分析手段,有助于优化程序性能和资源管理。 项目技术分析 obje...

gitblog_00522的博客 597

C++菱形继承对象内存布局实战讲解和分析

本文主要讲解C++对象模型中的菱形继承的对象模型,分别讨论基类对象变量和函数的继承问题。 何为菱形继承: 菱形继承是指一个基类(Base)派生出两个派生类(Derived1,Derived2),然后这两个派生类(Derived1,Derived2)派生出一个最终的派生类,如1.1的下图所示。 一、菱形继承之非虚继承 1.1类Base、派生类Derived1、派生类Derived2、最终派生类DDerived的UML结构图 1.2类Base、派生类Derived1、派生类Derived2、最终派生类DDer

净无邪博客 3099

C++对象内存池

对象内存池是一种有效的资源管理策略,能够显著提高性能,尤其是在高频率使用对象的场景中。通过合理的设计和实现,可以在保证性能的同时,降低内存管理的复杂性。在实际应用中,开发者需要根据具体需求和场景选择合适的对象池实现方式。

码事漫谈 1306

C++类对象的内存模型

虚函数表 #include using namespace std; class Father { public: Father(){}; void ShowName(){ cout"Father"<<endl; } }; class Son : public Father { public: Son(){}; void ShowNa

Sundrops的专栏 7176
上一篇: 不va_arg介绍(函数不定参数)
下一篇: 《红孩儿引擎内功心法修练与Cocos2d-x》之结点系统(场景,层,精灵)(精)
起风啰
博客等级 码龄16年 30粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值