![7dc605b581cef00d52794639f14d1308.png](https://i-blog.csdnimg.cn/blog_migrate/5b71687846cc5a2f7f38bf11096e9cba.png)
gdb分析C++对象内存布局(二):https://zhuanlan.zhihu.com/p/90770282
本文分析如下2个场景:
- 没有虚函数的类之间实现单继承;
- 有虚函数的类之间实现单继承。
单继承指的是派生类只继承一个基类。虚函数则是实现C++类对象多态的机制。
没有虚函数的类之间实现单继承
以下代码是没有虚函数的类之间实现单继承。
其中,代码(5)生成的对象的类跟要赋值的指针对应的类是不同的,指针对应的类是生成对象的类的基类,这种赋值操作称为upcast。
// hierarchy_test.cpp
对以上代码执行g++ -o hierarchy_test hierarchy_test.cpp -g -std=c++11进行编译。其中,-g参数使生成的可执行程序可以通过gdb进行调试。
执行./hierarchy_test后,输出如下。
(A
- 只有成员变量会占用类对象的内存空间,成员函数不占用。
- 经过upcast后的基类指针指向的地址与派生类指针指向的地址是一样的。
- 调用成员函数是根据调用者的类类型决定,也就是说,调用者是基类指针,则调用基类对应的成员函数,调用者是派生类指针,则调用派生类对应的成员函数。
有虚函数的类之间实现单继承
以下代码是有虚函数的类之间实现单继承。
// virtual_hierarchy_test.cpp
对以上代码执行g++ -o virtual_hierarchy_test virtual_hierarchy_test.cpp -g -std=c++11进行编译。
执行./virtual_hierarchy_test后,输出如下。
(A
- 除了成员函数会占用类对象的内存空间,还另外有一个指向虚函数表的虚函数表指针(vptr)会占用内存空间,且为了对齐,类对象的内存空间是8的倍数。
- 经过upcast后的基类指针指向的地址与派生类指针指向的地址是一样的。
- 调用成员虚函数是根据被调用者本身的类类型决定,也就是说,不管调用者的类类型是什么,最终调用的是被调用者对应的虚函数表里的函数,这就是虚函数的作用,虚函数实现了派生类的多态。
生成的派生类的对象内存布局如下。派生类的生成过程为:首先调用基类的构造函数,此时对象的虚函数表指针指向基类的虚函数表,接着调用派生类的构造函数,此时,一方面,“覆盖”原来的虚函数表指针,使指针指向派生类的虚函数表,另外一方面,增加派生类自己的成员变量到新生成的对象中。
![6ccc1413fe533ce68b7efd0cd06288f6.png](https://i-blog.csdnimg.cn/blog_migrate/ab5deb589ec36f0057679129de62d867.png)
通过gdb调试的方法验证生成的对象的内存布局。
对生成的可执行程序执行gdb virtual_hierarchy_test,设置break(断点设置在return语句处)并执行run之后,就可以对生成的对象的内存布局进行分析。
分析前先在gdb环境下执行如下命令,让内存空间的数据更具可读性。
(gdb
之后执行如下命令。
(gdb
根据输出结果分析如下:
- 0x614c20是派生类生成的对象d的起始地址,通过x/2xg 0x614c20可以获得该对象的内存空间数据,其中,0x0000000000400d10是虚函数表指针_vptr.A所保存的派生类的虚函数表的地址,0x000000280000000a则保存了两个int类型的变量(a=10,也就是0x0a,b=40,也就是0x28)。
- 根据_vptr.A = 0x400d10 <vtable for D+16>可知,派生类的虚函数表的起始地址应该是0x400d00,通过x/4xg 0x400d00可以获得虚函数表的内存空间数据,其中,0x0000000000400d40指向派生类的typeinfo,0x0000000000400a58指向函数D::func_a()。