5

上記の場所でこのコードがクラッシュする理由を詳しく説明していただけますか?私はこれに少し困惑しています。何か関係があると思いますが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;
}
4

7 に答える 7

16

sizeof(Derived)より大きいですsizeof(Base)。それが理由です。

Foo一般に、index でオブジェクトの配列にインデックスを付けるとi、次のように機能します。

element_address = array_base_address + i * sizeof(Foo)

配列要素が予想されるサイズでない場合、このインデックス付けがどのように壊れるかを見ることができます。一部のインデックスで機能する理由は、計算された要素アドレスがメモリ内の有効なオブジェクトを指す場合があるためです (ただし、実際にはith オブジェクトではありません)。

于 2009-06-25T11:32:49.553 に答える
13

配列を多態的に扱わないでください。C++ 言語はそれをサポートしていません。(この関連する質問も参照してください。)

于 2009-06-25T11:29:26.503 に答える
12

この FAQ からの引用:派生の配列はベースの配列と同じですか?

Derived は Base よりも大きく、2 番目のオブジェクト baseArray で行われるポインター演算は正しくありません: コンパイラは 2 番目のオブジェクトのアドレスを計算するときに sizeof(Base) を使用しますが、配列は Derived の配列です。つまり、計算されたアドレス (および後続のメンバー関数 f()) の呼び出しは、どのオブジェクトの先頭にもありません! Derived オブジェクトのど真ん中です。

于 2009-06-25T11:31:22.630 に答える
9

配列は、異なる型のオブジェクトを格納したり、オブジェクトをポリモーフィックに処理したりするために使用することはできません。代わりにポインタを格納してください。

BaseとのサイズDerivedが異なります - 配列を確認するコードにはBase[]、それが実際にDerived[]配列であることを検出する手段がなく、配列を誤ってインデックス付けするだけで、あらゆる種類の未定義の動作が発生します。

于 2009-06-25T11:30:58.170 に答える
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;
}
于 2009-06-25T11:47:46.827 に答える
2

エイドリアン・グリゴーレはこう答えました。

std::vector を使用すると、クラッシュすることはありません。

その後、コメントのために回答を削除しました:

この場合は構いません。同じようにクラッシュします。

しかし、Adrian はおおむね正しかった - MyWonderFulCode が or を取り、vector<Base>それをvector<Base>&orvector<Base>*に渡そうとするとvector<Derived>vector<Derived>*呼び出し元のコードはコンパイルされない。したがって、クラッシュもしないと思います。あるベクター型から別のベクター型に変換することはできません (少なくとも、新しいベクターを作成し、派生オブジェクトをそれにスライスしない限り)。

したがって、ある配列型から別の配列型に変換することもできないため、この場合もコンパイルの失敗が必要です。問題は、C/C++ が関数パラメーターとして渡されたときにポインターと配列の違いを認識できないことです。完全に賢明な from to の暗黙的なアップキャストのおかげで、 from Derived*toのBase*暗黙的なアップキャストに苦労していますが、これはほとんど常に壊れています。C++ の用語では、完全に不当な.Derived[]Base[]reinterpret_cast

これが、C++ プログラマーが (必要でない限り) 配列を使用しない理由の 1 つです。

于 2009-06-25T11:46:42.817 に答える
0

MyWonderfulCode 関数が Base 自体ではなく Baseポインターの配列を取るようにすると、その配列を多態的に使用できます。

于 2009-06-25T11:44:53.897 に答える