简体   繁体   English

c ++:下面一段代码崩溃了

[英]c++: following piece of code crashes

#include <iostream>
using namespace std;

class B
{
public:
    B() { cout << "Base B()" << endl; }
    ~B() { cout << "Base ~B()" << endl; }
private:
    int x;
};

class D : public B
{
public:
    D() { cout << "Derived D()" << endl; }
    virtual ~D() { cout << "Derived ~D()" << endl; }
};

int
main ( void )
{
    B* b = new D;
    delete b;
}


---- output----------
Base B()
Derived D()
Base ~B()
*** glibc detected *** ./a.out: free(): invalid pointer: 0x0930500c ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6[0xb7d41604]
/lib/tls/i686/cmov/libc.so.6(cfree+0x96)[0xb7d435b6]
/usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0xb7f24231]
./a.out[0x8048948]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7ce8775]
./a.out[0x80487c1]
Aborted

If i remove the private member "int x" from the base class, it works fine 如果我从基类中删除私有成员“int x”,它工作正常

基类B的析构函数也必须是虚拟的。

class B doesn't have a virtual destructor and you try to delete an instance of class D derived from class B through a pointer to class B - that's undefined behavior. class B没有虚拟析构函数,你试图通过指向class B的指针deleteclass B派生的class D的实例 - 这是未定义的行为。 You have to make class B destructor virtual to make your code work. 您必须使class B析构函数虚拟化才能使代码正常工作。

An alternative answer may be to use boost::shared_ptr : its templated constructors will remember that your object is of type D. 另一个答案可能是使用boost :: shared_ptr:它的模板化构造函数会记住你的对象是D类型。

#include <boost/shared_ptr.hpp>

int
main ( void )
{
    boost::shared_ptr<B> b( new D );
}

The above modification of your code will work fine, even without a virtual destructor. 即使没有虚拟析构函数,上面的代码修改也能正常工作。

By the way, unless you want to store pointers to D, there is no use in making D's destructor virtual. 顺便说一句,除非你想存储指向D的指针,否则将D的析构函数设为虚拟是没有用的。

What you are doing is UB, but for the specific compiler you are using behavior can be described as follows. 你正在做的是UB,但对于你正在使用的特定编译器行为可以描述如下。 To ease the ASCII-graphics below, modifying the example, adding a y member to D and modifying main . 为了简化下面的ASCII图形,修改示例,将y成员添加到D并修改main

#include <iostream>
using namespace std;

class B
{
public:
    B() { cout << "Base B()" << endl; }
    ~B() { cout << "Base ~B()" << endl; }
private:
    int x;
};

class D : public B
{
public:
    D() { cout << "Derived D()" << endl; }
    virtual ~D() { cout << "Derived ~D()" << endl; }
private:
    int y; // added.
};

int
main ( void )
{
    D* d = new D; // modified.
    B* b = d;
    delete b;
}

In the compiler you are using, the vtable, if any, is placed in the beginning of the memory block. 在您使用的编译器中,vtable(如果有)放在内存块的开头。 In the compiler you are using the memory layout for this is as follows: 在编译器中,您使用的内存布局如下:

+--------+
| vtable | <--- d points here, at the start of the memory block.
+--------+
| x      | <--- b points here, in the middle of the memory block.
+--------+
| y      |
+--------+

Later when calling delete b the program will try to free the memory block using the b pointer, which points to the middle of the memory block. 稍后当调用delete b ,程序将尝试使用b指针free内存块, b指针指向内存块的中间。

This will in turn result in the crash due to the invalid pointer error. 这将导致由于无效指针错误导致的崩溃。

Well, if you don't want a virtual destructor, then you must delete the object with a pointer to it's actual type: 好吧,如果你不想要虚拟析构函数,那么你必须用指向它的实际类型的指针删除该对象:

int
main ( void )
{
    D* as_d = new D;
    B *as_b = as_d;
    // you can use the object via either as_b or as_d but
    // you must delete it via as_d
    delete as_d;
}

That said, if you are not careful, it can be easy to delete the object through the wrong pointer. 也就是说,如果你不小心,可以很容易通过错误的指针删除对象。

So I know you don't want it, but for your own sanity, just make the B destructor virtual. 所以我知道你不想要它,但为了你自己的理智,只需将B析构函数设为虚拟。

I think the fact of being the integer member causing a memory crash, it's just a matter of "luck". 我认为作为整数成员导致内存崩溃的事实,这只是“运气”的问题。 I mean, if your destructor in the base class isn't virtual, the "destruction" of D isn't called. 我的意思是,如果基类中的析构函数不是虚拟的,则不会调用D的“破坏”。

So, in memory, your object D in the heaps could look like: 因此,在内存中,堆中的对象D可能如下所示:

  Object D
+-----------+
| B subobj. |
+-----------+
| D members |
+-----------+

If the B's destructor isn't virtual and if you delete a pointer base to B , the D part isn't destructed. 如果B的析构函数不是虚拟的,并且如果删除指向B的指针,则D部分不会被破坏。 In your case, the D part has size 0, and the B part a size of sizeof(int) (4 bytes), and that makes the situation a little more complicated to "guess", but perhaps you compiler is adding additional information for any reason to your objects in memory. 在你的情况下, D部分的大小为0, B部分的大小为sizeof(int) (4字节),这使得情况稍微复杂一些“猜测”,但也许您的编译器正在添加其他信息任何理由你的对象在内存中。

So, after the deletion of b , but before the end of the application, perhaps some piece of code adding by the compiler at exit time is causing the crash because of your unpropertly deletion of b (reusing of this part of memory for example or something like that). 因此,在删除b ,但在应用程序结束之前,编译器在退出时添加的某些代码可能导致崩溃,因为你无意中删除了b (例如重用这部分内存或某些东西)像那样)。

Since your code is very short, you could inspect the behaviour of your code with 'gdb', at assembly level, in the interval between the deletion of b and the termination of your code. 由于您的代码非常短,您可以在程序集级别使用'gdb'检查代码的行为,在删除b和终止代码之间的间隔内。

When you destroy derived class object using base class pointer then , it will lead to partial destruction of the object( ( only base class constructor is invoked ) if base class ctor is not virtual. 当您使用基类指针销毁派生类对象时,如果基类ctor不是虚拟的,它将导致对象的部分销毁((仅调用基类构造函数))。

so you must make base class desrtuctor as virtual. 所以你必须使基类desrtuctor成为虚拟的。

When a class is purely non virtual, it does not have an entry for the VPTR. 当一个类纯粹是非虚拟的时,它没有VPTR的条目。 Therefore B is exactly 4 bytes. 因此B恰好是4个字节。

Here is an illustration of the memory. 这是记忆的例证 VPTR is at the smallest memory location. VPTR位于最小的内存位置。 This is so that all derived classes know where to find the VPTR. 这样所有派生类都知道在哪里找到VPTR。 Hence, D is 8 bytes, the first 4 is VPTR, and the next 4 for x. 因此,D是8个字节,前4个是VPTR,接下来是4个x。

But, isn't D is-a B? 但是,不是D是-B? No, it works in the same way as multiple inheritance. 不,它的工作方式与多重继承相同。 When you assign a D address into a B pointer, the compiler knows that, and instead of giving you the REAL address of D, it gives you the address offsetted so that it works like a B. In this case, its really offset by 4 bytes. 当你将一个D地址分配给一个B指针时,编译器知道这一点,而不是给你真实的D地址,它会给你一个偏移的地址,使它像B一样工作。在这种情况下,它实际上偏移了4字节。 So when you try to B->x, you get the right value. 因此,当您尝试B-> x时,您将获得正确的值。

When you pass this offset address back to the heap in free, everything goes crazy, because what it needs is the original address. 当你将这个偏移地址免费传递回堆时,一切都变得疯狂,因为它需要的是原始地址。 This behavior is not undefined . 此行为未定义 It happens when in multiple inheritance as well. 它也发生在多重继承中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM