VS中C++对象的内存布局
本文主要简述一下在Visual Studio中C++对象的内存布局,这里没有什么测试代码,只是以图文的形式来描述一下内存分布,关于测试的代码以及C++对象模型的其他内容大家可以参考一下陈皓先生的几篇博文以及网上的其他一些文章:
《C++虚函数表解析》:http://blog.csdn.net/haoel/article/details/1948051
《C++对象的内存布局(上)》:http://blog.csdn.net/haoel/article/details/3081328
《C++对象的内存布局(下)》:http://blog.csdn.net/haoel/article/details/3081385
根据我自己调试出来的结果来看(Release版本),VS处理对象的原则大致可以分为以下几点:
1、 对于普通成员变量,按照声明次序以及内存的对齐原则存放。
例如对于类A,在32位程序中占据8个字节:
class A
{
public:
void test() { cout << "A::test\n"; }
private:
int nA;
char chA;
};
在内存中布局如下:

2、 如果对象含有虚函数,则在对象的开头处添加一个指针,该指针指向一个虚函数表,表中依序存放着该类中虚函数的地址。
例如对于类A,在32位程序中占据12个字节:
class A
{
public:
virtual void f() { cout << "A::f()\n"; }
virtual void fA() { cout << "A::fA()\n"; }
private:
int nA;
char chA;
};
在内存中布局如下,注意的是虚表的最后一项未必是0:

3、 如果对象只有直接继承(非虚拟继承)而来的父类,按照子类以及父类有没有虚函数可以分为以下几种情况:
<1>:子类和父类都没有虚函数,按照继承顺序先放置各个父类部分,再放置子类部分;
例如对于类B,在32位程序中占据24个字节:
class A1
{
private:
int nA1;
char chA1;
};
class A2
{
private:
int nA2;
char chA2;
};
class B : public A1, public A2
{
private:
int nB;
char chB;
};
在内存中布局如下:

<2>:子类有虚函数,父类没有虚函数,则先放置子类的虚函数表指针,再依次放置父类部分,最后放置子类部分;
例如对于类B,在32位程序中占据20个字节:
class A
{
private:
int nA;
char chA;
};
class B : public A
{
public:
virtual void f() { cout << "B::f()\n"; }
virtual void fB() { cout << "B::fB()\n"; }
private:
int nB;
char chB;
};
在内存中布局如下:

<3>:子类和父类都有虚函数,则先把父类列表中带有虚函数的父类放到前面,再依次放置没有虚函数的父类,最后放置子类部分(没有虚函数指针),同时修改各个虚函数表以及指针,使得其满足如下条件:第一个虚函数表指针所指向的虚函数表中先存放继承自本父类的虚函数地址,包括原样继承下来的以及重写的,再放置子类独有的虚函数地址,其余的虚函数表指针所指向的虚函数表只包含继承自对应父类的虚函数地址,包括原样继承下来的以及重写的。
例如对于类B,在32位程序中占据40个字节:
class A1
{
private:
int nA1;
char chA1;
};
class A2
{
public:
virtual void f() { cout << "A2::f()\n"; }
virtual void fA2() { cout << "A2::fA2()\n"; }
private:
int nA2;
char chA2;
};
class A3
{
public:
virtual void f() { cout << "A3::f()\n"; }
virtual void fA3() { cout << "A3::fA3()\n"; }
private:
int nA3;
char chA3;
};
class B : public A1, public A2, public A3
{
public:
virtual void f() { cout << "B::f()\n"; }
virtual void fA3() { cout << "B::fA3()\n"; }
virtual void fB() { cout << "B::fB()\n"; }
private:
int nB;
char chB;
};
在内存中布局如下:

4、(这一点还不太确定)如果对象有虚基类,无论是自己虚拟继承而来的还是父类虚拟继承而来的,则先按照以上规则将非虚基类部分处理完毕之后,再插入一个指针,再放置该类剩余的成员变量(该指针指向一个表格,表格中的每一项均是一个32位带符号整数——无论32位程序还是64位程序,其中第一项的内容是该指针到本类首部的偏移量,之后依次是该指针到本类虚基类的起始位置的偏移量),当这些全部放置完毕之后,然后再依次放置虚基类。这里有一个问题就是有的时候会在虚基类前面放置一个全零的指针,然而有的时候却又没有,按照我目前测试的结果来看,当子类有构造函数并且只要重写了虚基类的一个函数该虚基类前面就会有这个全零的指针。
例如对于下列程序,在32位程序中占用空间情况分别为:
class A
{
public:
virtual void f() { cout << "A::f()\n"; }
virtual void fA() { cout << "A::fA()\n"; }
private:
int nA;
char chA;
};
class B : public virtual A
{
public:
virtual void f() { cout << "B::f()\n"; }
virtual void fB() { cout << "B::fB()\n"; }
private:
int nB;
char chB;
};
class C
{
public:
virtual void f() { cout << "C::f()\n"; }
virtual void fC() { cout << "C::fC()\n"; }
private:
int nC;
char chC;
};
class D
{
public:
virtual void f() { cout << "D::f()\n"; }
virtual void fD() { cout << "D::fD()\n"; }
private:
int nD;
char chD;
};
class E
{
public:
virtual void f() { cout << "E::f()\n"; }
virtual void fE() { cout << "E::fE()\n"; }
private:
int nE;
char chE;
};
class F : public virtual B, public virtual C, public D, public virtual E
{
public:
F() {}
virtual void f() { cout << "F::f()\n"; }
virtual void fB() { cout << "F::fB()\n"; }
virtual void fC() { cout << "F::fC()\n"; }
virtual void fE() { cout << "F::fE()\n"; }
virtual void fF() { cout << "F::fF()\n"; }
private:
int nF;
char chF;
};
A、C、D、E均占12个字节,以A为例,内存布局如下:

B占28个字节,在内存中布局如下,值得注意的一点是,此时B因为已经重写了虚基类A的一个虚函数f(),所以如果再显示定义一个构造函数的话,在B中的A部分之前就会添加一个全0的指针,但是如果把构造函数或者f()的重写随便去掉一个,这个全0指针就不会存在了:

F占92个字节,在内存布局中如下,其中虚基类的排列顺序是按照列表里的顺序来的,比如在本例中,F虚拟继承的有B、C、E三个类,所以在虚基类的排放顺序中先放B,又因为B虚拟继承了A,所以最后的顺序是A、B、C、E。还有另外一点就是因为F定义了一个构造函数,所以虚基类前面会有一个全零指针,如果把这个构造函数去掉的话,F的大小就变成了76个字节,正好是92字节减去4个全零指针的大小:

VS中C++对象的内存布局的更多相关文章
- Java对象的内存布局以及对象所需内存大小计算详解
1. 内存布局 在HotSpot虚拟机中,对象的内存布局可以分为三部分:对象头(Header). 实例数据(Instance Data)和对齐填充(Padding). 1) 对象头(Header): ...
- JVM中,对象在内存中的布局
在hotSpot虚拟机中,对象在内存中的布局可以分成对象头.实例数据.对齐填充三部分. 对象头:主要包括: 1.对象自身的运行行元数据,比如哈希码.GC分代年龄.锁状态标志等,这部分长度在32位虚拟机 ...
- JVM中对象的内存布局与访问定位
一.对象的内存布局 已主流的HotSpot虚拟机来说, 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header).实例数据(Instance Data)和对齐填 ...
- C++ 对象的内存布局(上)
C++ 对象的内存布局(上) 陈皓 http://blog.csdn.net/haoel 点击这里查看下篇>>> 前言 07年12月,我写了一篇<C++虚函数表解析>的文 ...
- JVM——深入分析对象的内存布局
概述 一个对象本身的内在结构需要一种描述方式,这个描述信息是以字节码的方法存储在方法区中的.Class本身就是一个对象,都以KB为单位,如果new Integer()为了表示一个数据就占用KB级别的内 ...
- Java对象的内存布局
对象的内存布局 平时用java编写程序,你了解java对象的内存布局么? 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域: 对象头 实例数据 对齐填充 对象头 对象头包括两部分信息: ...
- jvm学习记录-对象的创建、对象的内存布局、对象的访问定位
简述 今天继续写<深入理解java虚拟机>的对象创建的理解.这次和上次隔的时间有些长,是因为有些东西确实不好理解,就查阅各种资料,然后弄明白了才来做记录. (此文中所阐述的内容都是以Hot ...
- C++ 对象的内存布局(上)
本文转载自haoel博主的博客:陈皓专栏 [空谷幽兰,心如皓月] 原文地址:C++ 对象的内存布局(上) C++ 对象的内存布局(上) 陈皓 http://blog.csdn.net/haoel 点击 ...
- JVM总结-java对象的内存布局
在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我们还可以通过反射机制.Object.clone 方法.反序列化以及 Unsafe.allocateInstance ...
随机推荐
- 启动Tomcat自动加载(运行)类
其实这是紧跟着我上次写的java计时器Timer的,因为Timer的测试类写好后,不可能要通过什么东西去触发,对已经存在的时间点进行监控 所以,在启动项目是自动运行此类 方法如下: 一.在web.xm ...
- cmd命令大全/cmd命令提示符大全
刚接触电脑的时候是从DOS系统开始,DOS时代根本就没有Windows这样的视窗操作界面,只有一个黑漆漆的窗口,让你输入命令.所以学DOS系统操作,cmd命令提示符是不可或缺的.可以告诉大家,大多数的 ...
- linux中grep使用方法具体解释
查找特定字符串并颜色显示 [root@fwq test]# grep -n 'the' regular_express.txt --color=auto 8:I can't finish the te ...
- 模块化的JavaScript
我们再一次被计算机的名词,概念笼罩. backbone.emberjs.spinejs.batmanjs 等MVC框架侵袭而来. CommonJS.AMD.NodeJS.RequireJS.SeaJS ...
- VS2012 黑色护眼主题
在黑色主题基础上,更改了字体 Ms Comic Sans 字号也增大了 附件中有两个 一个是原版主题下载自https://studiostyl.es/ 第二个是如下改完后的主题 vssettings. ...
- HTML构成及基本标签
超文本标记语言:HTML W3C:互联网联盟 注释语法:<!--注释掉的内容--> 标签格式: 双标签元素:<标签名 属性 style="样式">内容< ...
- KZ--NSString、NSMutableString
//NSString初始化的几种方法(3种方法) //1. NSString *str2 = [[NSString alloc] init]; ...
- WdatePicker日历控件使用方法
1. 跨无限级框架显示 无论你把日期控件放在哪里,你都不需要担心会被外层的iframe所遮挡进而影响客户体验,因为My97日期控件是可以跨无限级框架显示的 示例2-7 跨无限级框架演示 可无限跨越框架 ...
- MySql的一些问题
问题1:卸载重装mysql时,报1045和2003错误. 解决:点击skip,跳过这个错误.进到my.ini,在mysqld下面加一句:skip-grant-tables,保存.重启mysql服务,在 ...
- jcSQL词法分析器对字符串token的解析
上星期写完词法分析器的时候,曾遇上一个无关紧要却X疼的问题.毕竟是第一次完整地写整个语言的编译器(暂且这么叫着吧,解释器更靠谱),由于经验不足,在字符串解析这一块驻足了两天才解决掉,这里记录下来供以后 ...