虚拟继承崩溃应用程序

时间:2014-04-14 09:40:55

标签: c++ visual-studio visual-studio-2008 crash virtual-inheritance

以下代码崩溃(访问冲突错误),因为我使用了虚拟继承 AFAIK虚拟继承通过强制使用类的单个实例来解决Diamond问题。在这种情况下,Derived类仅继承IObject的一个实例,因此应该没有问题,但它会崩溃。

class IObject
{
public:
    virtual int getType()=0;
};
class Base : public IObject
{
protected:
    int val;
public:
    Base() { val = 1; }
    virtual int getType();
};
int Base::getType() { return val; }

class Derived : public virtual Base //If I remove the virtual keyword here problem is solved.
{
public:
    Derived() { val = 2; }
};

int getVal( void* ptr ) 
{
    return ((IObject*)ptr)->getType();
}

int main()
{
    void* ptr = new Derived();
    cout << getVal(ptr) << endl;
    return 0;
}

4 个答案:

答案 0 :(得分:3)

实际的崩溃来自于使用void *和cast与多重继承相结合(在这种情况下只使用虚拟继承)。正如James Kanze所指出的那样,因为转换为void *而不是回退到原始类型是未定义的行为,所以一切皆有可能。

使用c ++样式转换时,指针实际上已更改为允许使用多重继承。通过使用c style(和void *),编译器无法执行此操作,因此您使用不同的虚拟表访问IObject(如barak manos的答案所示)。

工作解决方案(没有空*):

#include <iostream>

class IObject
{
public:
    virtual int getType()=0;
};
class Base : public IObject
{
protected:
    int val;
public:
    Base() { val = 1; }
    virtual int getType();
};
int Base::getType() { return val; }

class Derived : public virtual Base
{
public:
    Derived() { val = 2; }
};

int getVal( IObject* ptr ) 
{
    return ptr->getType();
}

int main()
{
    IObject* ptr = new Derived();
    std::cout << getVal(ptr) << std::endl;
    return 0;
}

答案 1 :(得分:3)

这可能有助于您理解问题:

void* ptr = new Derived();
IObject* ptr1 = (IObject*)ptr;
IObject* ptr2 = new Derived();
IObject* ptr3 = new Derived();

当你在调试器中查看IObject指针时,这或多或少是你所看到的:

enter image description here

Derived的每个实例都有一个额外的成员变量(由编译器隐式添加到类的定义中),它指向类的虚函数表(也称为V-Table)。使用Visual Studio调试器,您可以在任何此类实例中以名称__vfptr

查看它

如上图所示,ptr2->__vfptrptr3->__vfptr正确指向地址0x01236834,该类的V-Table位于该地址。此表中第一个条目的值为0x012310f0,这是函数Base::getType的地址。

另一方面,ptr1->__vfptr指向地址0x0123683C。 &#34;该表中的第一个条目&#34;是0x00000000,显然任何函数的地址,因此您正在经历的内存访问冲突。

答案 2 :(得分:3)

问题在于转换链是不正确的:Derived* -> void* -> IObject*是由于混合C和C ++概念而导致的未定义行为。更具体地说,void*周围的规则是从C继承的,没有任何对象和层次结构的适应。

因此,解决方案是确保void*周期内的任何周期都是T -> void* -> T周期:始终采用相同的类型。因此,在您的情况下,您需要Derived* -> IObject* -> void* -> IObject*


要理解为什么virtual继承会导致问题,您必须了解它的具体表示具体(这是实现定义的)。让我们看一下可能的内存中表示的示例(基于Itanium ABI)。

线性非虚拟层次结构的实现就像通过组合:

struct Base { int a; };
struct Derived: Base { int b; };
struct SuperDerived: Derived { int c; };

+---+---+
| a | b |
+---+---+
^~~~~~~~~ Derived
    ^~~~~ Derived specific
^~~~~         Base

+---+---+---+
| a | b | c |
+---+---+---+
^~~~~~~~~~~~~ SuperDerived
        ^~~~~ SuperDerived specific
^~~~~~~~~     Derived
^~~~~         Base

在这种情况下,&derived == &base&superderived == &derived一般情况下(注意:如果一个图层没有虚拟表格而下一个图层没有,那么这就失去了作用)。

具有多个基础的层次结构

struct Base1 { int a; };
struct Base2 { int b; };
struct Derived: Base1, Base2 { int c; };

+---+---+---+
| a | b | c |
+---+---+---+
^~~~~~~~~~~~~ Derived
        ^~~~~ Derived specific
    ^~~~~     Base2
^~~~~         Base1

在这种情况下,&derived == &base1但是&derived != &base2,所以我们已经注意到基类不一定与其派生类具有相同的地址。

最后,让我们推动虚拟继承:

struct Object { int a; };
struct Base1: virtual Object { int b; };
struct Base2: virtual Object { int c; };
struct Derived: Base1, Base2 { int d; };

+---+---+
| b | a |
+---+---+
^~~~~~~~~ Complete Base1
^~~~~     Base1 specific
    ^~~~~ Object

+---+---+
| c | a |
+---+---+
^~~~~~~~~ Complete Base2
^~~~~     Base2 specific
    ^~~~~ Object

+---+---+---+---+
| b | c | d | a |
+---+---+---+---+
^~~~~~~~~~~~~~~~~ Complete Derived
        ^~~~~     Derived specific
^~~~~             Incomplete Base1
    ^~~~~         Incomplete Base2
            ^~~~~ Object

这里的挑战是应该在所有潜在基础之间共享虚拟基础的单个实例。由于只有完整的对象知道将涉及哪些基础,因此一个简单的选择是让完整的对象负责虚拟基础的放置(它放置在尾部)并让虚拟表提供导航的机制,运行时,从Object到派生类。

但是,请注意,我们的设计&base1 != &object&base2 != &object&derived != &object因为object放在尾部的情况如何。

这就是为什么使用 C ++机器来执行强制转换很重要的原因,它知道如何静态地或动态地(取决于具体情况)计算从一个基地到另一个基地时所需的指针调整。

注意:C ++机制知道计算是静态的还是动态的,例如static_cast<Base1*>(&object)是编译时错误,这里需要dynamic_cast

答案 3 :(得分:1)

崩溃是由reinterpret_cast引起的。

    void* ptr = new Derived();

实际上是

void* ptr = reinterpret_cast<void*>(new Derived());

当你这样做时,ptr不知道它指向的是什么。当你这样做

return ((IObject*)ptr)->getType();

意味着

return (reinterpret_cast<IObject*>(ptr))->getType();

这会调用未定义的IObject :: getType:因此崩溃。要解决它,请使用IObject *而不是void *

IObject* ptr = new Derived();
...
int getval(IObject* ptr)
{
    return ptr->getType();
}