C++类对应的内存结构

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

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

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

 

提示2:下文提到的“类大小”严格上来说是该类经过实例化的对象的大小。当然了,光研究长度的话,两者差别不大,因为:CClassA objA,sizeof(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成员的存在区域为静态区,可认为它们是“全局”的,只是不提供全局的访问而已,这跟C的static其实没什么区别。

 

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


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");};

};

长度:4(CPureVirtual),4(CDerivePV)

内存结构:


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位于最后面。

 

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

 

from:http://blog.csdn.net/guogangj/article/details/2036785#comments

时间: 2024-10-03 08:10:57

C++类对应的内存结构的相关文章

Oracle体系结构:内存结构和进程结构

oracle|进程|体系 (一)内存结构和进程结构 Oracle数据库的总体结构如下图:  1:Oracle实例(Instance)           在一个服务器中,每一个运行的Oracle数据库都与一个数据库实例相联系,实例是我们 访问数据库的手段.  实例在操作系统中用ORACLE_SID来标识,在Oracle中用参数INSTANCE_NAME来标识, 它们两个的值是相同的.数据库启动时,系统首先在服务器内存中分配系统全局区(SGA), 构成了Oracle的内存结构,然后启动若干个常驻内

c++类对象的内存模型

C++类对象内存结构 首先介绍一下C++中有继承关系的类对象内存的布局:在C++中,如果类中有虚函数,那么它就会有一个虚函数表的指针__vfptr,在类对象最开始的内存数据中.之后是类中的成员变量的内存数据. 对于子类,最开始的内存数据记录着父类对象的拷贝(包括父类虚函数表指针和成员变量).之后是子类自己的成员变量数据. 对于子类的子类,也是同样的原理.但是无论继承了多少个子类,对象中始终只有一个虚函数表指针. 为了探讨C++类对象的内存布局,先来写几个类和函数 首先写一个基类: class B

JVM内存结构

前言 在Java语言开发过程中,out of memory错误是很常见的一种错误.对于JVM的内存结构有更深入的了解,更更好的帮我们排查此类问题,有效的避免此类问题发生.在JAVA 8中内存结构有进行了改变,Metaspace替代了PermGen. 内存结构简介 JVM的内存结构如下图所示:(图片来源于网络) <img src="http://www.bo56.com/wp-content/uploads/2016/03/RDA.png" alt="RDA"

Java虚拟机的内存结构

我们都知道虚拟机的内存划分了多个区域,并不是一张大饼.那么为什么要划分为多块区域呢,直接搞一块区域,所有用到内存的地方都往这块区域里扔不就行了,岂不痛快.是的,如果不进行区域划分,扔的时候确实痛快,可用的时候再去找怎么办呢,这就引入了第一个问题,分类管理,类似于衣柜,系统磁盘等等,为了方便查找,我们会进行分区分类.另外如果不进行分区,内存用尽了怎么办呢?这里就引入了内存划分的第二个原因,就是为了方便内存的回收.如果不分,回收内存需要全部内存扫描,那就慢死了,内存根据不同的使用功能分成不同的区域,

JAVA之旅(三)——数组,堆栈内存结构,静态初始化,遍历,最值,选择/冒泡排序,二维数组,面向对象思想

JAVA之旅(三)--数组,堆栈内存结构,静态初始化,遍历,最值,选择/冒泡排序,二维数组,面向对象思想 我们继续JAVA之旅 一.数组 1.概念 数组就是同一种类型数据的集合,就是一个容器 数组的好处:可以自动给数组中的元素从0开始编号,方便操作这些元素 数组的格式 //公共的 类 类名 public class HelloJJAVA { // 公共的 静态 无返回值 main方法 数组 public static void main(String[] str) { /** * 格式:元素类型

浅谈C++中派生类对象的内存布局_C 语言

主要从三个方面来讲: 1 单一继承 2 多重继承 3 虚拟继承 1 单一继承 (1)派生类完全拥有基类的内存布局,并保证其完整性. 派生类可以看作是完整的基类的Object再加上派生类自己的Object.如果基类中没有虚成员函数,那么派生类与具有相同功能的非派生类将不带来任何性能上的差异.另外,一定要保证基类的完整性.实际内存布局由编译器自己决定,VS里,把虚指针放在最前边,接着是基类的Object,最后是派生类自己的object.举个栗子: class A { int b; char c; }

玩转JVM虚拟机:JVM内存结构

视频请在wifi下观看(土豪随意~),视频包含内容整理如下: 一,JVM内存结构 (图片来自网络) 1. 程序计数器 程序计数器(Program Counter Register),也有称作为PC寄存器.在汇编语言中,程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址,当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的

库缓存(Library Cache)内存结构

库缓存(Library Cache)内存结构 Library cache是Shared pool的一部分,它几乎是Oracle内存结构中最复杂的一部分. 一 , Library cache存放什么(存放的信息单元都叫做对象) ?   Library存放的信息单元都叫做对象,这些对象可以分为两类:  1. 存储对象  2. 过渡对象(游标Cursor,这里的游标指生成的可执行的对象, 运行相同SQL的多个进程可以共享该SQL产生的游标,节省内存.) A. 用户提交的SQL  B. SQL语句相关的

jvm系列(二):JVM内存结构

  JVM内存结构   所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能问题,那么这些问题就会变的非常常见,了解JVM内存也是为了服务器出现性能问题的时候可以快速的了解那块的内存区域出现问题,以便于快速的解决生产故障.   先看一张图,这张图能很清晰的说明JVM内存结构布局. Java的内存结构: JVM内存结构主要有三大块:堆内存.方法区和栈.堆内存是JVM中