为什么这段代码会在提到的地方崩溃?

时间:2009-06-25 11:25:05

标签: c++ arrays virtual

请您详细说明为什么此代码在提到的位置崩溃了?我有点难过。我想它与sizeof(int)有关,但我不太确定。有人可以解释一下吗?

class Base
{
public:
    virtual void SomeFunction() 
    {
        printf("test base\n");
    }

    int m_j;
};

class Derived : public Base {
public:
   void SomeFunction() 
   {
       printf("test derive\n");
   }

private:
   int m_i;
};

void MyWonderfulCode(Base baseArray[])
{
   baseArray[0].SomeFunction();  //Works fine
   baseArray[1].SomeFunction();  //Crashes because of invalid vfptr
   baseArray[2].SomeFunction();  //Crashes because of invalid vfptr
   baseArray[3].SomeFunction();  //Works fine
   baseArray[4].SomeFunction();  //Crashes because of invalid vfptr
   baseArray[5].SomeFunction();  //Crashes because of invalid vfptr
   baseArray[6].SomeFunction();  //Works fine
   baseArray[7].SomeFunction();  //Crashes because of invalid vfptr
   baseArray[8].SomeFunction();  //Crashes because of invalid vfptr
   baseArray[9].SomeFunction();  //Works fine
}
int _tmain(int argc, TCHAR* argv[])
{
   Derived derivedArray[10];
   MyWonderfulCode(derivedArray);
   return 0;
}

7 个答案:

答案 0 :(得分:16)

sizeof(Derived)大于sizeof(Base)。这就是原因。

通常,在索引Foo索引i个对象的数组的工作方式如下:

element_address = array_base_address + i * sizeof(Foo)

如果数组元素不是预期的大小,您可以看到此索引如何中断。为什么它适用于某些索引是因为有时计算的元素地址指向内存中的有效对象(但它实际上不是i对象。)

答案 1 :(得分:13)

永远不要多态地处理数组。 C ++语言不支持这一点。 (另见Related question。)

答案 2 :(得分:12)

引用此常见问题解答:Is array of derived same as as array of base?

  

Derived大于Base,用第二个对象baseArray完成的指针算法是   不正确:编译器在计算地址时使用sizeof(Base)   对于第二个对象,但数组是Derived数组,这意味着   计算的地址(以及后续的调用   成员函数f())甚至不在任何对象的开头!它的   在Derived对象的中间咂嘴。

答案 3 :(得分:9)

数组不能用于存储不同类型的对象或用于多态处理对象 - 而是存储指针。

BaseDerived的大小不同 - 看到Base[]数组的代码无法检测到它实际上是Derived[]数组并且只是错误地索引数组产生各种不确定的行为。

答案 4 :(得分:4)

这个问题叫做剪切。它有效地剥离了对象的继承部分,导致V表指向缺少的函数。

解决方案是使用指针数组。试试这个:

void MyWonderfulCode(Base* baseArray)
{
   baseArray[0].SomeFunction();  
   baseArray[1].SomeFunction();  
   baseArray[2].SomeFunction();  
   baseArray[3].SomeFunction();  
   baseArray[4].SomeFunction();  
   baseArray[5].SomeFunction();  
   baseArray[6].SomeFunction();  
   baseArray[7].SomeFunction();  
   baseArray[8].SomeFunction();  
   baseArray[9].SomeFunction();  
}
int _tmain(int argc, TCHAR* argv[])
{
   Base* derivedArray[10];

   for( int i = 0; i < 10; ++i ) derivedArray[i] = new Derived;

   MyWonderfulCode(derivedArray);
   return 0;
}

答案 5 :(得分:2)

Adrian Grigore回答:

  

如果你使用std :: vector,那么不应该有任何崩溃。

因为评论而删除了答案:

  

在这种情况下无关紧要。会以同样的方式崩溃。

但Adrian大部分都是正确的 - 如果MyWonderFulCode采用vector<Base>vector<Base>&vector<Base>*,并且您尝试将其传递给vector<Derived>vector<Derived>*,然后调用代码不编译。因此,我想,它也不会崩溃。从一种矢量类型转换为另一种矢量类型是不可能的(至少,不是没有创建新的矢量并将派生的对象切片到其中)。

因此,编译失败也是您在这种情况下所需要的,因为无法从一种数组类型转换为另一种数组类型。问题是当作为函数参数传递时,C / C ++无法区分指针和数组。由于从Derived*Base*的完全明智的隐式向上翻译,我们对从Derived[]Base[]的隐式向上翻译感到困惑,这几乎总是被打破。用C ++术语来说,这是一个完全没有道理的reinterpret_cast

这是C ++程序员不使用数组的原因之一(除非他们必须这样做)。

答案 6 :(得分:0)

使MyWonderfulCode函数采用Base 指针的数组而不是Base本身,然后您可以多态地使用该数组。