私は自分の質問で問題を解決したくないことに注意してください-私は物事が起こる可能性について考えていたので、何かについて疑問に思っていました:
オブジェクトを削除して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()
したがって、上記の条件では、仮想関数テーブルへのポインタは常に最初の削除によって上書きされるため、クラスに仮想デストラクタがある場合、次の削除はニルヴァーナにジャンプします。