web-dev-qa-db-ja.com

「純粋な仮想関数呼び出し」クラッシュはどこから発生しますか?

「純粋な仮想関数呼び出し」というエラーでコンピューターでクラッシュするプログラムに気づくことがあります。

抽象クラスのオブジェクトを作成できない場合、これらのプログラムはどのようにコンパイルされますか?

102
Brian R. Bondy

コンストラクターまたはデストラクターから仮想関数呼び出しを行おうとすると、それらが発生する可能性があります。コンストラクタまたはデストラクタから仮想関数呼び出しを行うことはできないため(派生クラスオブジェクトは構築されていないか、既に破棄されています)、基本クラスバージョンを呼び出します。これは、純粋な仮想関数の場合、存在しない。

(ライブデモを参照してください ここ

class Base
{
public:
    Base() { doIt(); }  // DON'T DO THIS
    virtual void doIt() = 0;
};

void Base::doIt()
{
    std::cout<<"Is it fine to call pure virtual function from constructor?";
}

class Derived : public Base
{
    void doIt() {}
};

int main(void)
{
    Derived d;  // This will cause "pure virtual function call" error
}
104
Adam Rosenfield

純粋な仮想関数を持つオブジェクトのコンストラクターまたはデストラクターから仮想関数を呼び出す標準的なケースと同様に、オブジェクトが破棄された後に仮想関数を呼び出すと、(少なくともMSVCで)純粋な仮想関数呼び出しを取得することもできます。明らかにこれは試してみるのはかなり悪いことですが、インターフェイスとして抽象クラスを使用しているときに混乱する場合は、表示される可能性があります。参照カウントされたインターフェイスを使用しており、参照カウントのバグがある場合、またはマルチスレッドプログラムでオブジェクトの使用/オブジェクトの破棄の競合状態がある場合は、可能性が高くなります。これらの種類のpurecallの特徴は、多くの場合、ctorとdtorの仮想呼び出しの「通常の容疑者」のチェックとして何が起こっているかを推測するのは簡単ではありません。

これらの種類の問題のデバッグを支援するために、MSVCのさまざまなバージョンで、ランタイムライブラリのpurecallハンドラを置き換えることができます。これを行うには、独自の関数に次のシグネチャを提供します。

int __cdecl _purecall(void)

ランタイムライブラリをリンクする前にリンクします。これにより、purecallが検出されたときに何が起こるかを制御できます。制御できたら、標準ハンドラーよりも便利なことができます。 purecallが発生した場所のスタックトレースを提供できるハンドラーがあります。こちらをご覧ください: http://www.lenholgate.com/blog/2006/01/purecall.html 詳細.

(_set_purecall_handler()を呼び出して、MSVCの一部のバージョンにハンドラーをインストールすることもできます)。

62
Len Holgate

通常、ダングリングポインターを介して仮想関数を呼び出すと、ほとんどの場合、インスタンスは既に破棄されています。

「創造的」な理由もあります。仮想関数が実装されたオブジェクトの一部を切り取った可能性があります。ただし、通常は、インスタンスが既に破棄されているだけです。

7
Braden

オブジェクトが破壊されたために純粋仮想関数が呼び出されるというシナリオに遭遇しました。Len Holgateすでに非常に素晴らしい answer があります。例を使って色を追加します。

  1. 派生オブジェクトが作成され、ポインターが(基本クラスとして)どこかに保存されます
  2. 派生オブジェクトは削除されますが、どういうわけかポインターはまだ参照されています
  3. 削除された派生オブジェクトを指すポインターが呼び出されます

Derivedクラスのデストラクターは、vptrポイントを純粋な仮想関数を持つBaseクラスvtableにリセットします。したがって、仮想関数を呼び出すと、実際には純粋な仮想関数を呼び出します。

これは、明らかなコードバグ、またはマルチスレッド環境での競合状態の複雑なシナリオが原因で発生する可能性があります。

簡単な例を次に示します(最適化をオフにしたg ++コンパイル-簡単なプログラムは簡単に最適化できます)。

 #include <iostream>
 using namespace std;

 char pool[256];

 struct Base
 {
     virtual void foo() = 0;
     virtual ~Base(){};
 };

 struct Derived: public Base
 {
     virtual void foo() override { cout <<"Derived::foo()" << endl;}
 };

 int main()
 {
     auto* pd = new (pool) Derived();
     Base* pb = pd;
     pd->~Derived();
     pb->foo();
 }

スタックトレースは次のようになります。

#0  0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff749b02a in __GI_abort () at abort.c:89
#2  0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x0000000000400f82 in main () at purev.C:22

ハイライト:

オブジェクトが完全に削除され、デストラクタが呼び出され、memroyが回収される場合、単にSegmentation faultメモリがオペレーティングシステムに戻ったため、プログラムはメモリにアクセスできません。そのため、この「純粋な仮想関数呼び出し」シナリオは、通常、オブジェクトがメモリプールに割り当てられ、オブジェクトが削除されたときに発生します。

1
Baiyan Huang

VS2010を使用し、パブリックメソッドからデストラクタを直接呼び出すと、実行時に「純粋な仮想関数呼び出し」エラーが発生します。

template <typename T>
class Foo {
public:
  Foo<T>() {};
  ~Foo<T>() {};

public:
  void SomeMethod1() { this->~Foo(); }; /* ERROR */
};

だから私は〜Foo()の中にあるものをプライベートメソッドを分離するために移動しました、そしてそれは魅力のように働きました。

template <typename T>
class Foo {
public:
  Foo<T>() {};
  ~Foo<T>() {};

public:
  void _MethodThatDestructs() {};
  void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};
0
David Lee

Borland/CodeGear/Embarcadero/Idera C++ Builderを使用している場合は、単に実装できます

extern "C" void _RTLENTRY _pure_error_()
{
    //_ErrorExit("Pure virtual function called");
    throw Exception("Pure virtual function called");
}

デバッグ中にコードにブレークポイントを配置し、IDEでコールスタックを確認します。それ以外の場合、適切なツールがある場合は、例外ハンドラ(またはその関数)でコールスタックをログに記録します。私は個人的にそのためにMadExceptを使用しています。

PS。元の関数呼び出しは[C++ Builder]\source\cpprtl\Source\misc\pureerr.cppにあります

0
Niki

何らかの内部的な理由で何らかの抽象クラス用に作成されたvtblがあると思います(何らかの実行時の型情報に必要な場合があります)。バグです。それだけでは、起こりえないことがあると言うべきです。

純粋な憶測

編集:私は問題のケースで間違っているようです。一部の言語のOTOH IIRCでは、コンストラクターデストラクターからのvtbl呼び出しが許可されています。

0
BCS