添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
gdb分析C++对象内存布局(一)

gdb分析C++对象内存布局(一)

gdb分析C++对象内存布局(二): zhuanlan.zhihu.com/p/90


本文分析如下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