私はC++で書かれたレガシーアプリケーションを維持しています。それは時々クラッシュし、Valgrindはいくつかのオブジェクトの二重削除を教えてくれます。
あなたが完全に理解していないアプリケーションで二重削除を引き起こしているバグを見つけるための最良の方法は何ですか?そしてそれは書き直すには大きすぎますか?
あなたの最高のヒントとコツを共有してください!
私はC++で書かれたレガシーアプリケーションを維持しています。それは時々クラッシュし、Valgrindはいくつかのオブジェクトの二重削除を教えてくれます。
あなたが完全に理解していないアプリケーションで二重削除を引き起こしているバグを見つけるための最良の方法は何ですか?そしてそれは書き直すには大きすぎますか?
あなたの最高のヒントとコツを共有してください!
そのような状況で私を助けてくれたいくつかの一般的な提案は次のとおりです。
cout << "class Foo constructed, ptr= " << this << endl;
、コードにいくつかのステートメントを挿入します(および対応するdelete
/ destructorの出力)。これはあなたのために働くかもしれないし、そうでないかもしれません。
昔、私は当時15歳だった100万以上の回線プログラムに取り組んでいました。まったく同じ問題に直面しました-巨大なデータセットで二重削除します。このようなデータがあれば、すぐに使用できる「メモリプロファイラー」は使用できません。
私の側にあったもの:
今トリック/アイデア:
my_malloc(int size)
{
static int allocation_num = 0; // it was single threaded
void* p = builtin_malloc(size+16);
*(int*)p = ++allocation_num;
*((char*)p+sizeof(int)) = 0; // not freed
return (char*)p+16; // check for NULL in order here
}
my_free(void* p)
{
if (*((char*)p+sizeof(int)))
{
// this is double free, check allocation_number
// then rerun app with this in my_alloc
// if (alloc_num == XXX) debug_break();
}
*((char*)p+sizeof(int)) = 1; // freed
//built_in_free((char*)p-16); // do not do this until problem is figured out
}
new / deleteを使用すると難しい場合がありますが、LD_PRELOADを使用すると、アプリを再コンパイルしなくてもmalloc/freeを置き換えることができる場合があります。
うん。@OliCharlesworthが言ったこと。ポインタが割り当てられたメモリを指しているかどうかをテストする確実な方法はありません。これは実際にはメモリの場所そのものであるためです。
あなたの質問が意味する最大の問題は、再現性の欠如です。それを念頭に置いて続けると、単純な「削除」構造を に変更することに行き詰まっていますdelete foo;foo = NULL;
。
それでも、最善のシナリオは、実際にそれを突き止めるまで、「発生が少ないように見える」ことです。
また、Valgrind が二重削除の問題であると示唆している証拠を尋ねたいと思います。そこに残っているより良い手がかりかもしれません。
これは、より単純で本当に厄介な問題の 1 つです。
Windows では、アプリが MSVC++ でビルドされていると仮定すると、標準ライブラリのデバッグ バージョンに組み込まれている広範なヒープ デバッグツールを利用できます。
Windows でもApplication Verifierを使用できます。私の記憶が正しければ、保護されたガードページを間に挟んで、各割り当てを個別のページに強制するモードがあります。これはバッファ オーバーランを見つけるのに非常に効果的ですが、ダブルフリーの状況にも役立つと思います。
(任意のプラットフォームで) できるもう 1 つのことは、(おそらくマクロを使用して) 変換されたソースのコピーを作成し、次のすべてのインスタンスが次のようになるようにすることです。
delete foo;
は次のように置き換えられます。
{ delete foo; foo = nullptr; }
(中括弧は多くの場合に役立ちますが、完全ではありません。) これにより、double-free の多くのインスタンスが null ポインター参照に変わり、検出がはるかに簡単になります。すべてをキャッチするわけではありません。古いポインターのコピーを持っているかもしれませんが、それは多くの一般的な削除後の使用シナリオを押しつぶすのに役立ちます。
これは便利だと思いました: backtrace() on linux。(-rdynamic を指定してコンパイルする必要があります。) これにより、すべてのメモリ操作 (新規/削除) の周りに try/catch ブロックを配置し、catch ブロックでスタック トレースを出力することにより、その二重の解放がどこから来ているかを知ることができます。
このようにして、valgrind を実行するよりもずっと速く容疑者を絞り込むことができます。
バックトレースを便利な小さなクラスにラップして、次のように言えます。
try {
...
} catch (...) {
StackTrace trace;
std::cerr << "Double free!!!\n" << trace << std::endl;
throw;
}
おそらく、削除を新しいバージョンとは異なる方法で処理したバージョンからアップグレードしています。
おそらく、以前のバージョンdelete
では、呼び出されたときに静的チェックをif (X != NULL){ delete X; X = NULL;}
行い、新しいバージョンではdelete
アクションを実行するだけでした。
ポインターの割り当てを調べて確認し、構築から削除までオブジェクト名の参照を追跡する必要がある場合があります。