gdb分析C++对象内存布局(一)
![浣小熊](https://pica.zhimg.com/e4d07a2d7ac5346a56c5523402a44450_l.jpg?source=172ae18b)
![](https://picx.zhimg.com/v2-4812630bc27d642f7cafcd6cdeca3d7a.jpg?source=88ceefae)
gdb分析C++对象内存布局(二): https:// zhuanlan.zhihu.com/p/90 770282
本文分析如下2个场景:
- 没有虚函数的类之间实现单继承;
- 有虚函数的类之间实现单继承。
单继承指的是派生类只继承一个基类。虚函数则是实现C++类对象多态的机制。
没有虚函数的类之间实现单继承
以下代码是没有虚函数的类之间实现单继承。
其中,代码(5)生成的对象的类跟要赋值的指针对应的类是不同的,指针对应的类是生成对象的类的基类,这种赋值操作称为upcast。
// hierarchy_test.cpp
#include <iostream>
class A {
public:
void func_a();
int a = 10;
class D: public A {
public:
void func_a();
int d = 40;
void A::func_a() {std::cout << "A::func_a()" << std::endl;}
void D::func_a() {std::cout << "D::func_a()" << std::endl;}
int main() {
std::cout << "sizeof(A): " << sizeof(A) << std::endl;
std::cout << "sizeof(D): " << sizeof(D) << std::endl;
D* d = new D();
std::cout << d << std::endl;
A* a = d; // (5)
std::cout << a << std::endl;
d->func_a();
a->func_a();
return 0;
对以上代码执行g++ -o hierarchy_test hierarchy_test.cpp -g -std=c++11进行编译。其中,-g参数使生成的可执行程序可以通过gdb进行调试。
执行./hierarchy_test后,输出如下。
sizeof(A): 4
sizeof(D): 8
0x614c20
0x614c20
D::func_a()
A::func_a()
- 只有成员变量会占用类对象的内存空间,成员函数不占用。
- 经过upcast后的基类指针指向的地址与派生类指针指向的地址是一样的。
- 调用成员函数是根据调用者的类类型决定,也就是说,调用者是基类指针,则调用基类对应的成员函数,调用者是派生类指针,则调用派生类对应的成员函数。
有虚函数的类之间实现单继承
以下代码是有虚函数的类之间实现单继承。
// virtual_hierarchy_test.cpp
#include <iostream>
class A {
public:
virtual void func_a();
int a = 10;
class D: public A {
public:
virtual void func_a();
int d = 40;
void A::func_a() {std::cout << "A::func_a()" << std::endl;}
void D::func_a() {std::cout << "D::func_a()" << std::endl;}
int main() {
std::cout << "sizeof(A): " << sizeof(A) << std::endl;
std::cout << "sizeof(D): " << sizeof(D) << std::endl;
D* d = new D();
std::cout << d << std::endl;
A* a = d;
std::cout << a << std::endl;
d->func_a();
a->func_a();
return 0;
对以上代码执行g++ -o virtual_hierarchy_test virtual_hierarchy_test.cpp -g -std=c++11进行编译。
执行./virtual_hierarchy_test后,输出如下。
sizeof(A): 16
sizeof(D): 16
0x614c20
0x614c20
D::func_a()
D::func_a()
- 除了成员函数会占用类对象的内存空间,还另外有一个指向虚函数表的虚函数表指针(vptr)会占用内存空间,且为了对齐,类对象的内存空间是8的倍数。
- 经过upcast后的基类指针指向的地址与派生类指针指向的地址是一样的。
- 调用成员虚函数是根据被调用者本身的类类型决定,也就是说,不管调用者的类类型是什么,最终调用的是被调用者对应的虚函数表里的函数,这就是虚函数的作用,虚函数实现了派生类的多态。
生成的派生类的对象内存布局如下。派生类的生成过程为:首先调用基类的构造函数,此时对象的虚函数表指针指向基类的虚函数表,接着调用派生类的构造函数,此时,一方面,“覆盖”原来的虚函数表指针,使指针指向派生类的虚函数表,另外一方面,增加派生类自己的成员变量到新生成的对象中。
通过gdb调试的方法验证生成的对象的内存布局。
对生成的可执行程序执行gdb virtual_hierarchy_test,设置break(断点设置在return语句处)并执行run之后,就可以对生成的对象的内存布局进行分析。
分析前先在gdb环境下执行如下命令,让内存空间的数据更具可读性。
(gdb) set print asm-demangle on
(gdb) set print demangle on
之后执行如下命令。
(gdb) p *a
$1 = {_vptr.A = 0x400d10 <vtable for D+16>, a = 10}
(gdb) p *d
$2 = {<A> = {_vptr.A = 0x400d10 <vtable for D+16>, a = 10}, d = 40}
(gdb) x/2xg 0x614c20
0x614c20: 0x0000000000400d10 0x000000280000000a
(gdb) x/4xg 0x400d00