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 上文是半年前对虚函数.虚拟继承的理解.可能 ...
随机推荐
- polay定理总结
参考资料:url=YveSCnlx_dlXo4oGjE8w3_2QRObRXvXMbix81ppCxBGqBswxffwDN9frzY5xxaKf7GgEMY8K93JTPEhkBzY66XOaN0G ...
- C#/.NET code
Some practices to write better C#/.NET code(译) C#(.NET)中有关编码的一些建议,原文地址:http://www.codeproject.com/Ar ...
- ChartType属性
xl3DArea:三维面积图 xl3DAreaStacked:三维堆积面积图 xl3DAreaStacked100:三维堆积面积图 xl3DBarClustered:三维簇状条形图 xl3DBarSt ...
- 【百度地图API】情人节求爱大作战——添加标注功能
原文:[百度地图API]情人节求爱大作战--添加标注功能 任务描述: 2月2日是除夕,2月14立马来!即将到来的情人节,你想送TA一份什么礼物呢? 不如,在你们居住的地方,画个大大的桃心,表达你对TA ...
- 讨论IT选定的技术招聘企业几点
在实际的招聘总结的几点思考,企业需要怎么样的人才,例如,以下个人总结: 1.技术能力是不是第一次 企业的时候,你往往看第一点的人的招聘是不是技术实力.但是,你的个人言论的行为和态度.能在半个小时内把你 ...
- Java中关于继承、类、多态、接口的知识点
继承 含义:在面向对象编程中,可以通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类 优点:1)代码的重用性:2)子类扩展父类的属性和方法:3)父类的属性和方法可用于子类:4)设计应用程序 ...
- .net EF 事物 订单流水号的生成 (一)
首先需要 添加 System.Transactions 程序集 数据表: create table SalesOrder ( ID ,) primary key not null, OrderNo ) ...
- MS SQL Server递归查询
原文:MS SQL Server递归查询 刚才在论坛上看到网友一个要求.参考如下,Insus.NET分析一下,可以使用MS SQL Server的递归查询,得到结果.准备一张表: 根据网友提供的数据, ...
- 怎样下载并编译Android4.0内核源代码goldfish(图文)
关于怎样下载Android4.0源代码,请查看我的博客内还有一篇文章(相同是图文教程): http://blog.csdn.net/flydream0/article/details/7036156 ...
- PHP实例——输出安全的HTML代码
原文:PHP实例--输出安全的HTML代码 //输出安全的htmlfunction h($text, $tags = null){ $text = trim($text); //完全过滤注释 $tex ...