当前位置: 首页 > news >正文

C++类对应的内存结构


提示1:对“内存结构”表示有疑问或不解的,先参考:

http://blog.csdn.net/guogangj/archive/2007/05/25/1625199.aspx

本文使用的表示方法和VC6Memory视图一致,即:左上表示低位。

 

提示2:下文提到的“类大小”严格上来说是该类经过实例化的对象的大小。当然了,光研究长度的话,两者差别不大,因为:CClassA objAsizeof(CClassA)sizeof(objA)得到的结果都是一样的。

 

一、真空类

class CNull

{

};

长度:1

内存结构:

??

评注:长度其实为0,这个字节作为内容没有意义,可能每次都不一样。

 

二、空类

class CNull2

{

public:

    CNull2(){printf("Construct\n");}

    ~CNull2(){printf("Desctruct\n");}

    void Foo(){printf("Foo\n");}

};

长度:1

内存结构:

??

评注:同真空类差不多,内部的成员函数并不会影响类大小。

 

三、简单类

class COneMember

{

public:

    COneMember(int iValue = 0){m_iOne = iValue;};

private:

    int m_iOne;

};

长度:4

内存结构:

00 00 00 00 //m_iOne

评注:成员数据才影响类大小。

 

四、简单继承

class CTwoMember:public COneMember

{

private:

    int m_iTwo;

};

长度:8

内存结构:

00 00 00 00 //m_iOne

CC CC CC CC //m_iTwo

评注:子类成员接在父类成员之后。

 

五、再继承

class CThreemember:public CTwoMember

{

public:

    CThreemember(int iValue=10) {m_iThree = iValue;};

private:

    int m_iThree;

};

长度:12

内存结构:

00 00 00 00 //m_iOne

CC CC CC CC //m_iTwo

0A 00 00 00 //m_iThree

评注:孙类成员接在子类之后,再再继承就依此类推了。

 

六、多重继承

class ClassA

{

public:

    ClassA(int iValue=1){m_iA = iValue;};

private:

    int m_iA;

};

 

class ClassB

{

public:

    ClassB(int iValue=2){m_iB = iValue;};

private:

    int m_iB;

};

 

class ClassC

{

public:

    ClassC(int iValue=3){m_iC = iValue;};

private:

    int m_iC;

};

 

class CComplex :public ClassA, public ClassB, public ClassC

{

public:

    CComplex(int iValue=4){m_iComplex = iValue;};

private:

    int m_iComplex;

};

 

长度:16

内存结构:

01 00 00 00  //A

02 00 00 00  //B

03 00 00 00  //C

04 00 00 00  //Complex

评注:也是父类成员先出现在前边,我想这都足够好理解。

 

七、复杂一些的继承

不写代码了,怕读者看了眼花,改画图。

长度:32

内存结构:

01 00 00 00 //A

02 00 00 00 //B

03 00 00 00 //C

04 00 00 00 //Complex

00 00 00 00 //OneMember

CC CC CC CC //TwoMember

0A 00 00 00 //ThreeMember

05 00 00 00 //VeryComplex

评注:还是把自己的成员放在最后。

 

只要没涉及到“虚”(Virtual),我想没什么难点,不巧的是“虚”正是我们要研究的内容。

 

八、趁热打铁,看“虚继承”

class CTwoMember:virtual public COneMember

{

private:

    int m_iTwo;

};

长度:12

内存结构:

E8 2F 42 00 //指针,指向一个关于偏移量的数组,且称之虚基类偏移量表指针

CC CC CC CC // m_iTwo

00 00 00 00 // m_iOne(虚基类数据成员)

评注:virtual让长度增加了4,其实是多了一个指针,关于这个指针,确实有些复杂,别的文章有具体分析,这里就不岔开具体讲了,可认为它指向一个关于虚基类偏移量的数组,偏移量是关于虚基类数据成员的偏移量。

 

九、“闭合”虚继承,看看效果

长度:24

内存结构:

14 30 42 00 //ClassB的虚基类偏移量表指针

02 00 00 00 //m_iB

C4 2F 42 00 //ClassC的虚基类偏移量表指针

03 00 00 00 //m_iC

04 00 00 00 //m_iComplex

01 00 00 00 //m_iA

评注:和预料中的一样,虚基类的成员m_iA只出现了一次,而且是在最后边。当然了,更复杂的情况要比这个难分析得多,但虚继承不是我们研究的重点,我们只需要知道:虚继承利用一个“虚基类偏移量表指针”来使得虚基类即使被重复继承也只会出现一次。

 

十、看一下关于static成员

class CStaticNull

{

public:

    CStaticNull(){printf("Construct\n");}

    ~CStaticNull(){printf("Desctruct\n");}

    static void Foo(){printf("Foo\n");}

    static int m_iValue;

};

长度:1

内存结构:(同CNull2

评注:可见static成员不会占用类的大小,static成员的存在区域为静态区,可认为它们是“全局”的,只是不提供全局的访问而已,这跟Cstatic其实没什么区别。

 

十一、带一个虚函数的空类

class CVirtualNull

{

public:

    CVirtualNull(){printf("Construct\n");}

    ~CVirtualNull(){printf("Desctruct\n");}

    virtual void Foo(){printf("Foo\n");}

};

长度:4

内存结构:

00 31 42 00 //指向虚函数表的指针(虚函数表后面简称“虚表”)

 

00423100:(虚表)

41 10 40 00 //指向虚函数Foo的指针

 

00401041:

E9 78 02 00 00 E9 C3 03  //函数Foo的内容(看不懂)

评注:带虚函数的类长度就增加了4,这个4其实就是个指针,指向虚函数表的指针,上面这个例子中虚表只有一个函数指针,值就是“0x00401041”,指向的这个地址就是函数的入口了。

 

十二、继承带虚函数的类

class CVirtualDerived : public CVirtualNull

{

public:

    CVirtualDerived(){m_iVD=0xFF;};

    ~CVirtualDerived(){};

private:

    int m_iVD;

};

长度:8

内存结构:

3C 50 42 00 //虚表指针

FF 00 00 00 //m_iVD

 

0042503C:(虚表)

23 10 40 00 //指向虚函数Foo的指针,如果这时候创建一个CVirtualNull对象,会发现它的虚表的内容跟这个一样

评注:由于父类带了虚函数,子类就算没有显式声明虚函数,虚表还是存在的,虚表存放的位置跟父类不同,但内容是同的,也就是对父类虚表的复制。

 

十三、子类有新的虚函数

class CVirtualDerived: public CVirtualNull

{

public:

    CVirtualDerived(){m_iVD=0xFF;};

    ~CVirtualDerived(){};

    virtual void Foo2(){printf("Foo2\n");};

private:

    int m_iVD;

};

长度:8

内存结构:

24 61 42 00 //虚表指针

FF 00 00 00 //m_iVD

 

00426124:(虚表)

23 10 40 00

50 10 40 00

评注:虚表还是只有一张,不会因为增加了新的虚函数而多出另一张来,新的虚函数的指针将添加在复制了的虚表的后面。

 

十四、当纯虚函数(pure function)出现时

class CPureVirtual

{

    virtual void Foo() = 0;

};

 

class CDerivePV : public CPureVirtual

{

    void Foo(){printf("vd: Foo\n");};

};

长度:4CPureVirtual),4CDerivePV

内存结构:

CPureVirtual:

(不可实例化)

 

CDerivePV:

28 50 42 00 //虚表指针

 

00425028:(虚表)

5A 10 40 00 //指向Foo的函数指针

评注:带纯虚函数的类不可实例化,因此列不出其“内存结构”,由其派生类实现纯虚函数。我们可以看到CDerivePV虽然没有virtual声明,但由于其父类带virtual,所以还是继承了虚表,如果CDerivePV有子类,还是这个道理。

 

十五、虚函数类的多重继承

前面提到:(子类的虚表)不会因为增加了新的虚函数而多出另一张来,但如果有多重继承的话情况就不是这样了。下例中你将看到两张虚表。

大小:24

内存结构

F8 50 42 00 //虚表指针

01 00 00 00 //m_iA

02 00 00 00 //m_iB

E8 50 42 00 //虚表指针

03 00 00 00 //m_iC

04 00 00 00 //m_iComplex

 

004250F8:(虚表)

5A 10 40 00 //FooA

55 10 40 00 //FooB

64 10 40 00 //FooComplex

 

004250E8:(虚表)

5F 10 40 00 //FooC

评注:子类的虚函数接在第一个基类的虚函数表的后面,所以B接在A后面,Complex接在B后面。基类依次出现,子类成员接在最后面,所以m_iComplex位于最后面。

 

本来还想看看更复杂些的情况,甚至包括虚继承和虚函数同时出现的多重多层继承情况,但确实有些复杂了,自己还有些找不到规律,所以准备之后再补充。

相关文章:

  • PM2 常用命令
  • 文因互联CEO鲍捷:做聊天机器人有哪些坑?
  • 使用Flash打造可定义界面风格的文件上传控件
  • go 入门学习笔记之 select + chan (十一)
  • HTML5 history API实践
  • 项目中使用RDLC报表
  • Workflow笔记2——状态机工作流
  • Octoroit OS VB操作系统简单介绍
  • 【C#】与C及OC的不同点
  • 10个小技巧帮助Devops走向成功
  • [UVA 11825] Hackers' Crackdown
  • Springboot集成Mybatis
  • 11.32 php扩展模块安装
  • vue跨域解决方法
  • 关于android Activity的 theme
  • Codepen 每日精选(2018-3-25)
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Iterator 和 for...of 循环
  • laravel with 查询列表限制条数
  • Less 日常用法
  • Material Design
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • ReactNative开发常用的三方模块
  • Sequelize 中文文档 v4 - Getting started - 入门
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • SpingCloudBus整合RabbitMQ
  • 十年未变!安全,谁之责?(下)
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 微信小程序填坑清单
  • 学习使用ExpressJS 4.0中的新Router
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • #前后端分离# 头条发布系统
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • (M)unity2D敌人的创建、人物属性设置,遇敌掉血
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (接口封装)
  • (循环依赖问题)学习spring的第九天
  • (转)h264中avc和flv数据的解析
  • (转)LINQ之路
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .NET 自定义中间件 判断是否存在 AllowAnonymousAttribute 特性 来判断是否需要身份验证
  • .net程序集学习心得
  • [ 常用工具篇 ] POC-bomber 漏洞检测工具安装及使用详解
  • []常用AT命令解释()
  • [20140403]查询是否产生日志
  • [Android开源]EasySharedPreferences:优雅的进行SharedPreferences数据存储操作
  • [Android学习笔记]ScrollView的使用
  • [AX]AX2012 R2 出差申请和支出报告
  • [BUG]Datax写入数据到psql报不能序列化特殊字符
  • [bzoj1006]: [HNOI2008]神奇的国度(最大势算法)
  • [dfs搜索寻找矩阵中最长递减序列]魔法森林的秘密路径
  • [Django ]Django 的数据库操作