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位于最后面。
本来还想看看更复杂些的情况,甚至包括虚继承和虚函数同时出现的多重多层继承情况,但确实有些复杂了,自己还有些找不到规律,所以准备之后再补充。
C++类对应的内存结构的更多相关文章
- Delphi 类与对象内存结构浅析(三篇)
http://blog.csdn.net/starsky2006/article/details/5497082 http://blog.csdn.net/starsky2006/article/de ...
- 黑马程序员——JAVA基础之函数,重载,内存结构
------- android培训.java培训.期待与您交流! ---------- 函数: 什么是函数? • 函数就是定义在类中的具有特定功能的一段独立小程序. • 函数也称为方法. 函数的格 ...
- Java内存结构、类的初始化、及对象构造过程
概述 网上关于该题目的文章已经很多,我觉得把它们几个关联起来讲可能更好理解一下.与其它语言一样,它在执行我们写的程序前要先分配内存空间,以便于存放代码.数据:程序的执行过程其实依然是代码的执行及数据的 ...
- jvm系列(二):JVM内存结构
JVM内存结构 所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢?其实如果你经常解决服务器性能 ...
- JVM之内存结构
JVM是按照运行时数据的存储结构来划分内存结构的.JVM在运行Java程序时,将他们划分成不同格式的数据,分别存储在不同的区域,这些数据就是运行时数据.运行时数据区域包括堆,方法区,运行时常量池,程序 ...
- Oracle之内存结构(SGA、PGA)
一.内存结构 SGA(System Global Area):由所有服务进程和后台进程共享: PGA(Program Global Area):由每个服务进程.后台进程专有:每个进程都有一个PGA. ...
- 浅析JVM内存结构和6大区域(转)举例非常好
内存作为系统中重要的资源,对于系统稳定运行和高效运行起到了关键的作用,Java和C之类的语言不同,不需要开发人员来分配内存和回收内存,而是由JVM来管理对象内存的分配以及对象内存的回收(又称为垃圾回收 ...
- [转]oracle学习入门系列之五内存结构、数据库结构、进程
原文地址:http://www.2cto.com/database/201505/399285.html 1 Oracle数据库结构 关于这个话题,网上一搜绝对一大把,更别提书籍上出现的了,还有很多大 ...
- JVM内存结构、垃圾回收那点事
翻看电脑的文件夹,无意看到了9月份在公司做的一次分享,浏览了一下"婆婆特",发现自己在ppt上的写的引导性问题自己也不能确切的回答出来,哎,知识这东西,平时不常用的没些日子就生疏了 ...
随机推荐
- 段和RSEG用法
RSEG是段选择指令,要想明白它的意思就要了解段的意思. 段是程序代码或数据对象的存储单位.程序代码放到代码段,数据对象放到数据段.段分两种,一是绝对段,一是再定位段.绝对段在汇编语言中指定,在用L5 ...
- C#中Split分隔字符串的应用(C#、split、分隔、字符串)
转载地址 .用字符串分隔: using System.Text.RegularExpressions; string str="aaajsbbbjsccc"; string[] s ...
- bzoj1630 [Usaco2007 Demo]Ant Counting
Description Bessie was poking around the ant hill one day watching the ants march to and fro while g ...
- Path Sum 解答
Question Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that addi ...
- HDOJ2553-N皇后问题(DFS)
N皇后问题 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Sub ...
- redhat6.3 64位更新源(使用网易源)全过程记录
本篇博客参考:http://chinaxiaoyu.diandian.com/post/2013-01-24/40046529897.首先在浏览器中输入http://tel.mirrors.163.c ...
- python语言磁力搜索引擎源码公开,基于DHT协议
原文地址: http://www.cnblogs.com/huangxie/p/5550680.html
- UE是什么意思?用户体验设计师与UE设计是什么关系?
本文来自:http://blog.sina.com.cn/s/blog_a6bebb3f01015h47.html 什么是UE(UE——UE就是用户体验度) 你会发觉他的使用很人性化:功能强大但操作简 ...
- Servlet页面间对象传递的方法
Servlet页面间对象传递的方法 1.request 2.session 3.application 4.cookie 5.其它的
- Android应用程序与SurfaceFlinger服务之间的共享UI元数据(SharedClient)的创建过程分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/7867340 在前面一篇文章中,我们分析了And ...