Cpp 对象模型探索 / 多重继承下基类指针释放子类对象的原理说明(虚析构函数的作用)

源码

#include <iostream>

class Base1
{
public:
    virtual void func_1_1(){ std::cout << "Base1::func_1_1()" << std::endl; }
    virtual void func_1_2(){ std::cout << "Base1::func_1_2()" << std::endl; }

    virtual ~Base1() {}
};

class Base2
{
public:
    virtual void func_2_1(){ std::cout << "Base2::func_2_1()" << std::endl; }
    virtual void func_2_2(){ std::cout << "Base1::func_2_2()" << std::endl; }

    virtual ~Base2() {}
};

class Son :public Base1, public Base2
{
public:
    virtual void func_s(){ std::cout << "Son::func_s()" << std::endl; }
    virtual void func_2_1(){ std::cout << "Son::func_2_1()" << std::endl; }
};

int main()
{
    Base2 *pb2 = new Son();

    delete pb2;
    return 0;
}

分析

Son类的内存布局如下所示:

在代码 delete pb2; 处打下断点,进入汇编,如下图所示:

总结过程,如下:

delete pb2 时,根据 Base2 的虚函数表,找到 trunk 项,从而获得了子类对象的首地址,并且调用子类对象的虚析构函数。然后先后执行了 Base2、Base1和Son类的虚构函数,最后完成 new Son 对象的释放。

若是如下代码:

    Base1 *pb1 = new Son();
    delete pb1;

则 Base1 虚函数表中的 trunk 项对应的代码中就不包含了调整 pb1 指针的位置,因为 pb1 本身就指向了 Son 类对象的首地址,所以直接调用了 Son 类对象的析构函数。

拓展:

1、虚函数表中的 trunk 项,实际上一段汇编代码的首地址,执行的内容如下:

  • 将 pb2 偏移到子类对象的首地址。
  • 执行子类对象的虚析构函数。

2、上述释放内存的成功实现的前提是,基类的析构函数必须是虚函数。若不是,则直接执行了 Base2 那部分的析构函数,这就导致了 Son 类对象时只释放 Base2 对应的那部分。

对于操作系统的内存分配机制来说,相对于内存块前面的固定位置会记录着该内存块的信息,包括该内存块的字节数。由于上述操作中,释放 Base2 对应的部分是时候,没有办法找到这部分的内存信息,从而导致了释放内存失败,程序运行崩溃。

 

(SAW:Game Over!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值