6

私は自分の質問で問題を解決したくないことに注意してください-私は物事が起こる可能性について考えていたので、何かについて疑問に思っていました:

オブジェクトを削除してgccをコンパイラとして使用するとどうなりますか?

先週、競合状態がオブジェクトの二重削除につながるクラッシュを調査していました。

オブジェクトの仮想デストラクタを呼び出すときにクラッシュが発生しました。これは、仮想関数テーブルへのポインタがすでに上書きされているためです。

仮想関数ポインタは最初の削除によって上書きされますか?

そうでない場合、その間に新しいメモリ割り当てが行われない限り、2番目の削除は安全ですか?

なぜ以前に問題が認識されなかったのか疑問に思っています。唯一の説明は、最初の削除中に仮想関数テーブルがすぐに上書きされるか、2番目の削除がクラッシュしないことです。

(1つ目は、「競合」が発生した場合に常に同じ場所でクラッシュが発生することを意味します。2つ目は、競合が発生しても通常は何も発生しません。その間に3番目のスレッドが削除オブジェクトを上書きした場合にのみ、問題が発生します。 )。


編集/更新:

テストを行いましたが、次のコードがsegfault(gcc 4.4、i686、amd64)でクラッシュします。

class M
{
private:
  int* ptr;
public:
  M() {
  ptr = new int[1];
  }
  virtual ~M() {delete ptr;}
};

int main(int argc, char** argv)
{
  M* ptr = new M();
  delete ptr;
  delete ptr;
}

「仮想」をdtorから削除すると、ダブルフリーが検出されるため、プログラムはglibcによって中止されます。'virtual'を使用すると、仮想関数テーブルへのポインタが無効であるため、デストラクタへの間接関数呼び出しを実行するとクラッシュが発生します。

amd64とi686の両方で、ポインターは有効なメモリ領域(ヒープ)を指していますが、そこにある値は無効です(カウンター?非常に低い、たとえば0x11、0x21)ので、コンパイラーの場合は「呼び出し」(または「jmp」) return-optimizationを実行しました)無効な領域にジャンプします。

プログラム受信信号SIGSEGV、

セグメンテーション違反。0x0000000000000021

の ??()(gdb)

#0 0x0000000000000021 in ?? ()

#1 0x000000000040083e in main()

したがって、上記の条件では、仮想関数テーブルへのポインタは常に最初の削除によって上書きされるため、クラスに仮想デストラクタがある場合、次の削除はニルヴァーナにジャンプします。

4

3 に答える 3

6

何かを2回削除することは未定義の動作です。それ以上の説明は必要ありません。通常、1つを探すのは無益です。それはプログラムをクラッシュさせるかもしれませんが、そうではないかもしれませんが、それは常に悪いことであり、あなたがそれをした後、プログラムは常に未知の状態になります。

于 2010-07-31T15:48:00.350 に答える
6

これは、メモリアロケータ自体の実装に大きく依存しており、アプリケーションに依存する障害は、オブジェクトのvテーブルを上書きすることは言うまでもありません。多数のメモリアロケータスキームがあり、それらはすべて機能とdouble free()に対する耐性が異なりますが、これらはすべて1つの共通のプロパティを共有しています。2番目のfree()の後のある時点でアプリケーションがクラッシュします。

クラッシュの理由は、通常、メモリアロケータが、割り当てられた各メモリチャンクの前(ヘッダー)と後(フッター)に少量のメモリを割り当てて、実装固有の詳細を格納するためです。ヘッダーは通常、チャンクのサイズと次のチャンクのアドレスを定義します。フッターは通常、チャンクのヘッダーへのポインターです。通常、2回削除するには、少なくとも隣接するチャンクが空いているかどうかを確認する必要があります。したがって、次の場合にプログラムがクラッシュします。

1)次のチャンクへのポインタが上書きされ、2番目のfree()により、次のチャンクにアクセスしようとしたときにセグメンテーション違反が発生します。

2)前のチャンクのフッターが変更され、前のチャンクのヘッダーにアクセスすると、セグメンテーション違反が発生します。

アプリケーションが存続する場合は、free()がさまざまな場所でメモリを破損しているか、すでに空きチャンクの1つと重複する空きチャンクを追加して、将来的にデータが破損することを意味します。最終的に、プログラムは、破損したメモリ領域を含む次のfree()またはmalloc()のいずれかでセグメンテーション違反を起こします。

于 2010-07-31T16:15:35.950 に答える
1

delete2回(または)実行するfreeと、メモリがすでに再割り当てされている可能性があり、delete再度実行するとメモリが破損する可能性があります。割り当てられたメモリブロックのサイズは、多くの場合、メモリブロック自体の直前に保持されます。

派生クラスがある場合は、派生クラス(子)に対してdeleteを呼び出さないでください。仮想として宣言されていない場合は、~BaseClass()デストラクタのみが呼び出され、割り当てられたメモリをから残しDerivedClassて永続化およびリークします。これは、解放する必要DerivedClassのあるメモリを超えて割り当てられた追加のメモリがあることを前提としています。BaseClass

すなわち

BaseClass* obj_ptr = new DerivedClass;  // Allowed due to polymorphism.
...
delete obj_ptr;  // this will call the destructor ~Parent() and NOT ~Child()
于 2010-08-01T01:14:42.737 に答える