c++对象内存布局的理解
引言
结合网上的一些资料,通过自己的一番摸索,得出了一点个人见解。现在写下来,希望与各位同学共同探讨,共同进步。 
以下所有代码均是在VS2012下测试。
一个普通的基类
1: #include <iostream>
2: using namespace std;
3:
4: class Base
   5: {
6: public:
7: Base():
8: i(0)
   9:     {
10: }
  11:     void test(){
12: cout << "Base::test" << " i = " << i << endl;
13: }
14: virtual void virtualTest()
  15:     {
16: cout << "Base::virtualTest" << " i = " << i << endl;
17: }
18: static void staticTest()
  19:     {
20: cout << "Bae::staticTest" << endl;
21: }
22:
23:
24:
25: private:
26: int i;
27:
28: };
29:
30: int main()
  31: {
32: Base b;
33: b.test();
34: b.virtualTest();
35: Base::staticTest();
36:
37: return 0;
38: }
我们定义了一个Base类,其类成员不言自明。在第31行处打断点,调试模式下运行。通过观察b对象,可以得到下图:
 
此时b还未初始化,我们可以在watch窗口中,双击b,在b前面加上”&”符号,得到b的地址。同理,将i也添加如watch窗口中,然后可得如下图:
我们可以看到,对象b的地址为0x003afc00,变量i的地址为0x003afc04。同时,我们可以通过Type列看到其类型的变化(通过这个,我们很容易写出相应的代码来验证其显示的数据)。按f10运行至第32行。

我们可以看到b的结构里包含了两个成员及其他们的地址:_vfptr 和 i。我们先来聊聊i。
数据成员
我们通过观察比较对象b的地址与i的地址,得出一个结论:i的地址是对象b的地址加上一个int*单位(即4字节)。现在,我们来验证我们的结论。

添加37、38两行代码,将b的地址强制转换为int*,然后将1赋值给b的地址增加一个int*单位后的地址。我们调试运行至39行,可以看到如下图:

i的值确实变了!证明我们的结论没错。数据成员的值存放在对象地址的+4偏移位置上。但是还有些疑问,如果i是个char类型的,内存布局会怎么样? 是不是存放在&b加一个char*单位(即1字节)地址上呢?如果有多个数据成员呢?这些请大家自行验证。通过观察他们的地址,我们得出最后的结论如下:不管数据成员的类型是什么,第一个定义的数据成员的地址总是与对象的地址相差四个字节,如果有多个数据成员,后面的数据成员的地址将根据第一个数据成员的地址加上其自身的类型的指针单位长度(比如char,则+1,float则+4)。
成员函数
在watch窗口中,对象b包含的成员,除了i,就只有一个_vfptr了,它是什么呢?那么多成员函数呢?我们仔细观察_vfptr的结构:

发现_vfptr好像是一个数组一样的结构,但是又不尽然。从图上我们可以看出,它本身是一个void**类型的结构,其“0下标”处存放的是一个void*类型,而且看起来像是virtualTest这个函数。根据图,我们有以下猜想:普通的成员函数和静态成员函数的存放位置,在类的对象中并没有保存。类对象中仅保存了虚函数的信息。又依据35行调用静态成员函数的代码,我们可以得到:在类中定义的静态成员函数,等同于在一个普通的函数上面包了一层命名空间。至于普通的成员函数,在这篇里暂且不表。下面我们来验证_vtptr是否是保存着对象的虚函数信息,以及是如何保存的。
我们通过图,可以看到三个地址信息:&b即对象的地址为0x0046fa10,_vfptr的地址为0x0024dc74,可能是表示虚函数的“[0]”的地址为0x002410d7,直观上看,他们毫无物理上联系,他们的地址相隔很大。为了称呼方便,我们用虚表(Virtual Table)来指称_vfptr,用虚函数virtualTest来指称“[0]”。
首先,我们看一下0x0046fa10地址上到底存的是什么,通过按Alt+6,我们可以呼出memory窗口,该窗口显示了相应内存地址存放的信息。

该窗口大概的内容布局如红色部分所示。通过在address栏输入0x002af8f4(下图b的地址),我们可以看到其中存放的是 74 dc f6 00。现在再结合虚表的地址0x00f6dc74来看,是不是有那么点联系?脑中是否立马浮现了一些大端机、小端机之类的信息?没错,因为我的机器是小端机,所以0x002af8f4中存的数据是0x00f6dc74。即虚表的地址。现在两者的逻辑关系很明显了,只要这样写即可得到虚表的地址:

如37行代码所示,我们将b的地址强制转换成int*,然后解引用,即可得到一个int值。我们将vbAdd添加到watch窗口中查看(注意将value调成16进制显示),得到结果如下:

是的,我们的确得到了虚表的地址!我们按照前面的步骤,看是否能得到虚函数virtualTest的地址。在内存窗口中查询得到如下结果:

果真如此,通过虚表地址存放的值,即可找到虚函数virtualTest的地址。下面我们用代码验证一下。

不出所料。我们得到了虚函数virtualTest的地址。现在三个地址(对象b、虚表、虚函数virtualTest)之间的逻辑关系很清晰了:对象b地址上存放的是指向虚表地址的数据,虚表地址上存放的是指向虚函数地址的数据。
既然我们得到了虚函数的地址,那么我们是不是可以手工来调用他们?如同前面通过地址来给类的私有数据成员赋值一样。让我们来写代码验证一下

我们可以看到,确实能够通过函数指针调用虚函数virtualTest,但是他的输出却出乎我们的意料。why?很遗憾,我目前也不知道原因。
总结
通过此篇,我们可以了解到在一个无继承关系的普通的类中,其虚函数和数据成员的内存布局,它们之间的内在物理上和逻辑上的关系是怎么样的。此外,还有一个问题:为什么通过地址调用虚函数,其输出的值为什么不是预期的呢?
由于个人水平有限,写的不是很详尽正确。如果同学们发现了错误,烦请指正。也请有能力答复上述疑问的同学,不吝笔墨。
参考资料
- 韩宏.老码识图.北京:电子工业出版社,2012
 - 陈皓博客:http://blog.csdn.net/haoel/article/details/1948051/
 
c++对象内存布局的理解的更多相关文章
- 我对c++对象内存布局的理解
		
引言 结合网上的一些资料,通过自己的一番摸索,得出了一点个人见解.现在写下来,希望与各位同学共同探讨,共同进步. 以下所有代码均是在VS2012下测试. 一个普通的基类 1: #include < ...
 - 图说C++对象模型:对象内存布局详解
		
0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看. 本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有 ...
 - c++ 对象内存布局详解
		
今天看了的,感觉需要了解对象内存的问题.参考:http://blog.jobbole.com/101583/ 1.何为C++对象模型? 引用<深度探索C++对象模型>这本书中的话: 有两个 ...
 - c++对象内存布局
		
这篇文章我要简单地讲解下c++对象的内存布局,虽然已经有很多很好的文章,不过通过实现发现有些地方不同的编译器还是会有差别的,希望和大家交流. 在没有用到虚函数的时候,C++的对象内存布局和c语言的st ...
 - 好文章系列C/C++——图说C++对象模型:对象内存布局详解
		
注:收藏好文章,得出自己的笔记,以查漏补缺! ------>原文链接:http://blog.jobbole.com/101583/ 前言 本文可加深对C++对象的内存布局.虚表指针.虚 ...
 - 使用sos查看.NET对象内存布局
		
前面我们图解了.NET里各种对象的内存布局,我们再来从调试器和clr源码的角度来看一下对象的内存布局.我写了一个测试程序来加深对.net对象内存布局的了解: using System; using S ...
 - 【转载】图说C++对象模型:对象内存布局详解
		
原文: 图说C++对象模型:对象内存布局详解 正文 回到顶部 0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看.本文的结论都在 ...
 - 浅析GCC下C++多重继承 & 虚拟继承的对象内存布局
		
继承是C++作为OOD程序设计语言的三大特征(封装,继承,多态)之一,单一非多态继承是比较好理解的,本文主要讲解GCC环境下的多重继承和虚拟继承的对象内存布局. 一.多重继承 先看几个类的定义: 01 ...
 - C++对象内存布局测试总结
		
C++对象内存布局测试总结 http://hi.baidu.com/����/blog/item/826d38ff13c32e3a5d6008e8.html 上文是半年前对虚函数.虚拟继承的理解.可能 ...
 
随机推荐
- NET ERP系统架构设计
			
解析大型.NET ERP系统架构设计 Framework+ Application 设计模式 我对大型系统的理解,从数量上面来讲,源代码超过百万行以上,系统有超过300个以上的功能,从质量上来讲系统应 ...
 - crawler_网络爬虫之数据分析_httpwatcher
			
所谓爬虫,首先要通过各种手段爬取到想要站点的数据. web2.0之后,各种网络站点类型越来越多,早期的站点多为静态页面[html .htm],后来逐步加入 jsp.asp,等交互性强的页面.再后来随着 ...
 - iOS开发之protocol和delegate
			
protocol--协议 协议是用来定义对象的属性,行为和用于回调的. 协议中有两个keyword@private和@optional,@private表示使用这个协议必需要写的方法,@op ...
 - Content-Type boundary 问题
			
我并不知道问题怎么描述清楚一些. 事情是这样的,使用 Microsoft Dynamics CRM Server 2016 做CRM系统的时候用到 使用 Web API 执行批处理操作(参见SDK或 ...
 - 我的MYSQL学习心得(九)
			
原文:我的MYSQL学习心得(九) 我的MYSQL学习心得(九) 我的MYSQL学习心得(一) 我的MYSQL学习心得(二) 我的MYSQL学习心得(三) 我的MYSQL学习心得(四) 我的MYSQL ...
 - ASP.NET 5 Hello World
			
ASP.NET 5系列教程 (二):Hello World 本篇文章内容比较基础,主要是向大家展示如何创建一个 ASP.NET 5 工程,主要包含内容如下: 创建ASP.NET 5 工程 添加 T ...
 - PhotoShop CC安装抠图插件KnockOut 2
			
1.KnockOut 2只有32位版本,因此需要给32位的PhotoShop CC安装. 2.下载地址:http://www.cr173.com/soft/28207.html 3.安装KnockOu ...
 - SQL点滴5—产生时间demention,主要是时间转换
			
原文:SQL点滴5-产生时间demention,主要是时间转换 数据仓库中有时间表,存储时间信息,这个存储过程接收开始时间结束时间,写入时间具体信息.有高手用excel函数功能很快能产生INSERT语 ...
 - outlook 会议室
			
原文:outlook 会议室 但是,里面的方法只能用于发送普通电子邮件.如果要发起会议之类的特殊邮件的话,可以C#调用Outlook API,自身的API. 创建项目后,为它添加.NET引用:“Mic ...
 - GIMP也疯狂之动态图的制作(四)
			
本篇文章为gimp制作动态图的第四篇.在之前的基础上简单的拓展了下思路.就能做出蛮实用的动态图.本文将介绍两个动态图.第一个为在一张静态图上添加动态图,第二个图为修改部分渐变. 效果: 素材: 其实, ...