4

このプログラムを検討してください。

int main()
{
    struct test
    {
        test() { cout << "Hello\n"; }
        ~test() { cout << "Goodbye\n"; }

        void Speak() { cout << "I say!\n"; }
    };

    test* MyTest = new test;
    delete MyTest;

    MyTest->Speak();

    system("pause");
}

私はクラッシュを予期していましたが、代わりにこれが起こりました:

こんにちは
さようなら
私は言います!

これは、メモリが割り当て解除としてマークされている場合、物理的にワイプされておらず、コードがそれをすぐに参照しているため、オブジェクトは完全に無傷でそこに残っているためだと思います。呼び出す前に割り当てがSpeak()多ければ多いほど、クラッシュする可能性が高くなります。

理由が何であれ、これは私の実際のスレッドコードの問題です。上記の場合、現在のスレッドがアクセスしたいオブジェクトを別のスレッドが削除したかどうかを確実に判断するにはどうすればよいですか?

4

10 に答える 10

8

これを検出するプラットフォームに依存しない方法はありません。他のスレッドが、できればクリティカル セクション内または同等のオブジェクトを削除した後に、ポインタを NULL に設定する必要があります。

簡単な解決策は、これが発生しないようにコードを設計することです。他のスレッドが必要とする可能性のあるオブジェクトを削除しないでください。共有リソースは、安全であることを確認してからクリアしてください。

于 2010-10-21T17:05:27.547 に答える
4

クラッシュを予期していましたが、代わりに次のことが起こりました。

これは、Speak() がクラスのメンバーにアクセスしていないためです。コンパイラはポインターを検証しないため、他の関数呼び出しと同様に Speak() を呼び出し、(削除された) ポインターを非表示の「this」パラメーターとして渡します。Speak() はそのパラメーターにアクセスしないため、クラッシュする理由はありません。

于 2010-10-21T18:42:04.390 に答える
3

クラッシュを予期していましたが、代わりに次のことが起こりました。

未定義の動作とは、何でも起こり得ることを意味します。

于 2010-10-21T17:04:22.917 に答える
2

上記を踏まえて、現在のスレッドがアクセスしたいオブジェクトを別のスレッドが削除したかどうかを確実に確認するにはどうすればよいでしょうか?

MyTestポインターをゼロ (または NULL) に設定するのはどうですか。これにより、それがもはや有効ではないことが他のスレッドに明らかになります。(もちろん、他のスレッドが同じメモリを指す独自のポインタを持っている場合は、間違った設計をしています。他のスレッドが使用する可能性のあるメモリを削除しないでください。)

また、このように機能することは絶対に期待できません。それは幸運でした。一部のシステムでは、削除するとすぐにメモリが破損します。

于 2010-10-21T17:06:05.470 に答える
2

削除されたオブジェクトへのアクセスを回避するように設計を改善することが最善ですが、デバッグ機能を追加して、削除されたオブジェクトにアクセスする場所を見つけることができます。

  • すべてのメソッドとデストラクタを仮想化します。
  • コンパイラが、vtable へのポインタがオブジェクトの前にあるオブジェクト レイアウトを作成することを確認します
  • デストラクタで vtable へのポインタを無効にする

この汚いトリックにより、すべての関数呼び出しがポインターが指すアドレスを読み取り、ほとんどのシステムで NULL ポインター例外が発生します。デバッガーで例外をキャッチします。

すべてのメソッドを仮想化することに躊躇する場合は、抽象基本クラスを作成して、このクラスから継承することもできます。これにより、仮想機能を簡単に削除できます。クラス内で仮想化する必要があるのはデストラクタだけです。

struct Itest
{
    virtual void Speak() = 0;
    virtual void Listen() = 0;
};

struct test : public Itest
{
    test() { cout << "Hello\n"; }
    virtual ~test() { 
        cout << "Goodbye\n";

        // as the last statement!
        *(DWORD*)this = 0;  // invalidate vtbl pointer
    }

    void Speak() { cout << "I say!\n"; }
    void Listen() { cout << "I heard\n"; }
};
于 2010-10-21T17:36:11.643 に答える
1

この状況では、参照カウントを使用できます。割り当てられたオブジェクトへのポインターを逆参照するコードは、カウンターをインクリメントします。それが完了すると、減少します。その際、カウントがゼロになると削除が発生します。オブジェクトのすべてのユーザーがルールに従っている限り、割り当て解除されたオブジェクトには誰もアクセスできません。

マルチスレッドの目的では、条件が真であることをコードが「期待」しないようにする設計原則に従うことが最善であるという他の回答に同意します。元の例から、オブジェクトの割り当てが解除されたかどうかを確認する方法として例外をキャッチするつもりでしたか? それは信頼できる副作用ではなく、最後の手段としてのみ使用したい信頼できる副作用であったとしても、副作用に頼っているようなものです.

于 2010-10-21T17:13:10.233 に答える
1

これは、未定義の動作を呼び出しているため、他の場所で何かが削除されているかどうかを「テスト」する信頼できる方法ではありません。つまり、キャッチする例外がスローされない可能性があります。

代わりに、std::shared_ptrorを使用boost::shared_ptrして参照を数えます。を使用してコンテンツを強制的shared_ptrに削除することができshared_ptr::reset()ます。その後、 を使用して後で削除されたかどうかを確認できますshared_ptr::use_count() == 0

于 2010-10-21T18:17:46.527 に答える
0

valgrind のような静的およびランタイム アナライザーを使用してこれらを確認することもできますが、それはコードの構造と言語の使用方法に関係しています。

于 2010-10-21T17:07:02.697 に答える
0
// Lock on MyTest Here.
test* tmp = MyTest;
MyTest = NULL;
delete tmp;
// Unlock MyTest Here.

if (MyTest != NULL)
    MyTest->Speak();
于 2010-10-21T17:08:09.477 に答える
0

最もエレガントではない1つのソリューション...

オブジェクトのリストにミューテックスを配置します。オブジェクトを削除するときは、null としてマークします。オブジェクトを使用する場合は、null を確認してください。アクセスがシリアル化されるため、一貫した操作が可能になります。

于 2010-10-21T17:08:32.057 に答える