【问题标题】:Non virtual destructor in base class, but virtual destructor in derived class cause segmentation fault基类中的非虚拟析构函数,但派生类中的虚拟析构函数导致分段错误
【发布时间】:2013-11-01 18:13:58
【问题描述】:

最近在一次求职面试中,有人问我当基类的析构函数未声明为虚拟时,派生类中的内存泄漏问题。

我写了一个小测试来确认我的答案,但我发现了一些有趣的东西。显然,如果您通过new 创建了Derived 对象,但将其指针存储为Base*,则如果删除了指针,则不会调用派生对象的析构函数(对于我对这个问题的回答)。

我认为在这种情况下派生类的析构函数是否为虚拟无关紧要,但在我的系统上,以下代码显示了其他情况:

#include <iostream>
#include <string>

// just a helper class, printing its name out when it is destructed
class PrintOnDestruct
{
    public:
        PrintOnDestruct( const std::string& name )
        : name_( name )
        {}

        ~PrintOnDestruct()
        {
            std::cout << "Destructing: " << name_ << std::endl;
        }

    protected:

        std::string name_;
};

// the Base class
class Base
{
    public:
        Base()
        {
            print_on_destruct_ = new PrintOnDestruct( "Base" );
        }

        // the destructor is NOT virtual!
        ~Base()
        {
            delete print_on_destruct_;
        }

    protected:

        PrintOnDestruct* print_on_destruct_;

};

// the NonVirtualDerived class, doesn't have a virtual destructor either
class NonVirtualDerived : public Base
{
    public:
        NonVirtualDerived()
        : Base()
        {
            print_on_destruct_child_ = new PrintOnDestruct( "NonVirtualDerived" );
        }

        // the destructor is NOT virtual!
        ~NonVirtualDerived()
        {
            delete print_on_destruct_child_;
        }

    protected:

        PrintOnDestruct* print_on_destruct_child_;

};

// the VirtualDerived class does have a virtual destructor 
class VirtualDerived : public Base
{
    public:
        VirtualDerived()
        : Base()
        {
            print_on_destruct_child_ = new PrintOnDestruct( "VirtualDerived" );
        }

        // the destructor is virtual!
        virtual ~VirtualDerived()
        {
            delete print_on_destruct_child_;
        }

    protected:

        PrintOnDestruct* print_on_destruct_child_;

};

int main()
{
    // create the two child classes
    Base* non_virtual_derived = new NonVirtualDerived;
    Base* virtual_derived = new VirtualDerived;

    // delete the two objects
    delete non_virtual_derived; // works as expected (only calls Base's destructor, the memory of NonVirtualDerived will be leaked)
    delete virtual_derived; // segfault, after calling Base's destructor

    return 0;
}

本以为程序会输出以下两行并正常退出:

Destructing: Base
Destructing: Base

我得到了那个输出,但是在第二行之后程序立即退出并出现分段错误。和消息:

*** Error in `...': free(): invalid pointer: 0x00000000006020e8 ***

我已将两次调用的顺序更改为delete,但程序在调用delete virtual_derived; 时总是会出现段错误。谁能告诉我为什么会这样?

【问题讨论】:

  • 尝试更改 Base* 分配以使用 static_cast 而不是隐式强制转换,看看是否有任何变化。可能不会,但在这种情况下最好还是明确一点。
  • 通过指向具有非虚拟析构函数的基类的指针进行删除只是未定义的行为。
  • @hyde 使用 static_cast 不会改变任何东西。
  • @SebastianSchneider 是的,隐式转换应该等于显式static_cast。尽管事后看来,正如当前最佳答案中所解释的那样,需要使用虚拟方法(或多重继承)进行强制转换(隐式或显式)表明指针值可能因类型而异。

标签: c++ inheritance polymorphism destructor


【解决方案1】:

答案真的在于陈述:

    Base* virtual_derived = new VirtualDerived;

您正在尝试“释放”一个未被“malloc”返回的地址。要了解原因,请将此行替换为

    VirtualDerived* x = new VirtualDerived;
    Base* virtual_derived = x;

如果您打印这两个地址,您会注意到 'x' 和 'virtual_derived' 具有不同的值。 “malloc”返回的地址(通过“new”)是“x”,传递给“free”的地址(通过“delete”)是“virtual_derived”。

【讨论】:

  • 你是对的,地址实际上是不同的。我为 NonVirtualDerived 尝试了相同的方法,但在这种情况下地址保持不变。我在哪里可以找到关于为什么指针在第一种情况下发生变化的信息?
  • 您能解释一下为什么地址不同吗?我很好奇。谢谢
  • 它的变化是因为在分配内存的开头添加了 vtable 到 VirtualDerived。所以Base*应该指向vtable之后。
  • @AndyT 感谢您的解释。我可以确认Base* 指向更远。在一项测试中,我有 0xad30e0 (VirtualDerived*) 与 0xad30e8 (Base*)
  • @SebastianSchneider:现在我们都知道您使用的是 x64 系统 :)
【解决方案2】:

您必须将析构函数在基类中声明为virtual,在本示例中您没有这样做。仅在派生类中将其声明为 virtual 是不够的。

【讨论】:

  • 这并不能解释段错误。
  • @hyde:在未定义的行为得到纠正之前,讨论段错误是没有意义的。
  • 问题的答案似乎只是“未定义的行为”。
  • 在基础中需要一个虚拟 dtor 的笼统声明是错误的。只要对象通过指向其动态类型或具有虚拟 dtor 的基类型的指针被销毁,这是完全可以接受的。 UB 仅在删除时被调用。
  • 我知道,如果将基类析构函数设为虚拟,则整个问题都已解决,但我在这里明确探讨了这个问题,如果不是,会发生什么。如果答案是,使析构函数在基类中不是虚拟的,会导致未定义的行为,那没关系。但是我想知道在哪里可以找到该定义。
猜你喜欢
  • 2011-11-16
  • 2021-08-22
  • 2021-03-20
  • 2015-02-24
  • 2013-11-03
  • 2018-07-06
  • 2012-06-26
  • 2013-12-08
  • 2018-06-29
相关资源
最近更新 更多