8

現在、クラッシュログをデバッグしています。クラッシュは、(c ++-)オブジェクトのvtableポインターが0x1であるために発生しますが、クラッシュログからわかる限り、オブジェクトの残りの部分は問題ないようです。

仮想メソッドを呼び出そうとすると、プログラムがクラッシュします。

私の質問:どのような状況でvtableポインターがnullになる可能性がありますか?演算子deleteはvtableポインターをnullに設定しますか?

これは、gcc 4.0.1(Apple Inc.ビルド5493)を使用するOSXで発生します。

4

5 に答える 5

9

記憶の踏みつけかもしれません-何かvtableが誤ってそれを上書きしています。C++でこれを「達成」する方法はほぼ無限にあります。たとえば、バッファオーバーフロー。

于 2010-01-15T14:38:41.480 に答える
8

あなたが持っているあらゆる種類の未定義の振る舞いは、この状況につながる可能性があります。例えば:

  • プログラムが無効なメモリに書き込む原因となるポインタ演算などのエラー。
  • 初期化されていない変数、無効なキャスト...
  • 配列を多形的に処理すると、これが二次的な影響として発生する可能性があります。
  • 削除後にオブジェクトを使用しようとしています。

質問も参照してください。実際に可能な未定義動作の最悪の例は何ですか?C ++プログラマーが知っておくべき一般的な未定義動作は何ですか?

最善の策は、大量のデバッグを支援するために、境界とメモリチェッカーを使用することです。

于 2010-01-15T14:38:52.200 に答える
7

非常に一般的なケース:コンストラクターから純粋仮想メソッドを呼び出そうとしています...

コンストラクター

struct Interface
{
  Interface();
  virtual void logInit() const = 0;
};

struct Concrete: Interface()
{
  virtual void logInit() const { std::cout << "Concrete" << std::endl; }
};

ここで、次の実装を想定します。Interface()

Interface::Interface() {}

その後、すべてがうまくいきます:

Concrete myConcrete;
myConcrete.pure();    // outputs "Concrete"

コンストラクターの後でpureと呼ぶのはとても面倒ですが、コードを正しく因数分解する方がよいでしょうか?

Interface::Interface() { this->logInit(); } // DON'T DO THAT, REALLY ;)

そうすれば一行でできます!!

Concrete myConcrete;  // CRASHES VIOLENTLY

なんで ?

オブジェクトはボトムアップで構築されているためです。それを見てみましょう。

クラスを構築するための指示Concrete(大まかに)

  1. (もちろん)十分なメモリと_vtableにも十分なメモリを割り当てます(仮想関数ごとに1つの関数ポインタ、通常は宣言された順序で、左端のベースから開始します)

  2. コンストラクターを呼び出すConcrete(表示されないコード)

    a>コンストラクターを呼び出しInterfaceます。コンストラクターは_vtableをそのポインターで初期化します

    b>Interfaceコンストラクターの本体を呼び出す(あなたはそれを書いた)

    c>これらのメソッドの_vtable内のポインターをオーバーライドする具体的なオーバーライド

    d>Concreteコンストラクターの本体を呼び出す(あなたはそれを書いた)

だから問題は何ですか ?さて、見てb>注文してくださいc>;)

コンストラクター内からメソッドを呼び出すと、virtual期待したことは実行されません。ポインタを検索するために_vtableに移動しますが、_vtableはまだ完全には初期化されていません。したがって、重要なことはすべて、次の効果です。

D() { this->call(); }

実際には:

D() { this->D::call(); }

コンストラクター内から仮想メソッドを呼び出す場合、構築されるオブジェクトの完全な動的タイプではなく、現在のコンストラクターの静的タイプが呼び出されます。

私のInterface/のConcrete例では、これはInterface型を意味し、メソッドは仮想純粋であるため、_vtableは実際のポインターを保持しません(たとえば、コンパイラーがデバッグ値をセットアップするのに十分な友好的である場合、0x0または0x01)。

デストラクタ

偶然にも、デストラクタのケースを調べてみましょう;)

struct Interface { ~Interface(); virtual void logClose() const = 0; }
Interface::~Interface() { this->logClose(); }

struct Concrete { ~Concrete(); virtual void logClose() const; char* m_data; }

Concrete::~Concrete() { delete[] m_data; } // It's all about being clean
void Concrete::logClose()
{
  std::cout << "Concrete refering to " << m_data << std::endl;
}

では、破壊するとどうなりますか?_vtableはうまく機能し、実際のランタイムタイプが呼び出されます...ただし、ここで意味するのは未定義の動作です。m_data削除された後、Interfaceデストラクタが呼び出される前に何が起こったのか誰が知っているのでしょうか。私はしません ;)

結論

コンストラクタまたはデストラクタ内から仮想メソッドを呼び出さないでください。

そうでない場合は、メモリの破損、頑張ってください;)

于 2010-01-15T20:21:58.837 に答える
4

私の最初の推測は、いくつかのコードがmemset()クラスオブジェクトを処理しているということです。

于 2010-01-15T15:16:40.940 に答える
1

これは完全に実装に依存します。ただし、削除後に他の操作によってメモリスペースがnullに設定される可能性があると想定するのは非常に安全です。

他の可能性には、いくつかの緩いポインタによるメモリの上書きが含まれます-実際、私の場合、ほとんどの場合これです...

とはいえ、削除後にオブジェクトを使用しようとしないでください。

于 2010-01-15T14:40:06.970 に答える