2008-09-15 11 views
16

スマートポインタによって引き起こされるメモリリークを発見する "技術"を知っている人はいますか?私は現在、参照カウントでスマートポインタを頻繁に使用するC++で書かれた大きなプロジェクトに取り組んでいます。当然のことながら、スマートポインタによってメモリリークが発生しています。スマートポインタは、コードのどこかで参照されているため、メモリが解放されません。 "不必要な"参照でコード行を見つけることは非常に難しく、対応するオブジェクトはフリーではありません(それはもはや使用されません)。スマートポインタによって引き起こされるメモリリークを見つける

私は、参照カウンタのインクリメント/デクリメント操作のコールスタックを収集することを提案しているWebでいくつかのアドバイスを見つけました。これは私に良いヒントを与えてくれます。どのコードによって参照カウンターが増減しましたか?

しかし、私が必要とするのは、対応する「増加/減少コールスタック」をグループ化する何らかの種類のアルゴリズムです。これらの呼び出しスタックのペアを削除した後、(少なくとも)1つの "呼び出しスタックを増やす"ことが残っていると、対応するオブジェクトが解放されないようにする、「不必要な」参照を持つコード部分が表示されます。今はリークを修正することは大したことではありません!

しかし、誰かがグループ化を行う「アルゴリズム」のアイディアを持っていますか?

開発はWindows XPで行われます。

(私は...説明しようとしたものを、誰かが理解願っています)

編集:私は循環参照に起因するリークについて話しています。

+0

私は理解しています...プログラミング言語やプラットフォームをより具体的に参照できますか?メモリリークは、その取り扱いが異なる可能性があります。 – zxcv

+0

スマートポインタは、一般にC++/STLを意味します。 –

+0

ここには混乱があります。私はポインタを持っていない割り当てられたメモリ(スマートポインタでは起こらないはずです)、循環参照に起因するリーク、およびポインタが保持されているので不必要に拡張されたオブジェクトを区別する価値があると思います。 –

答えて

15

なお、一つのソースを作成したところでわかります。たとえば、AにはBへのスマートポインタがあり、BにはAへのスマートポインタがあります。AもBも破壊されません。あなたは、依存関係を見つけて、それから壊しなければなりません。

可能であれば、ブーストスマートポインタを使用し、データの所有者となるポインタにはshared_ptrを使用し、削除を呼び出さないポインタにはweak_ptrを使用します。

1

私があなただったら、私は(私はRubyである)のログを取ると、次のような何かをするための簡単なスクリプトを記述します。

def allocation?(line) 
    # determine if this line is a log line indicating allocation/deallocation 
end 

def unique_stack(line) 
    # return a string that is equal for pairs of allocation/deallocation 
end 

allocations = [] 
file = File.new "the-log.log" 
file.each_line { |line| 
    # custom function to determine if line is an alloc/dealloc 
    if allocation? line 
    # custom function to get unique stack trace where the return value 
    # is the same for a alloc and dealloc 
    allocations[allocations.length] = unique_stack line 
    end 
} 

allocations.sort! 

# go through and remove pairs of allocations that equal, 
# ideally 1 will be remaining.... 
index = 0 

while index < allocations.size - 1 
    if allocations[index] == allocations[index + 1] 
    allocations.delete_at index 
    else 
    index = index + 1 
    end 
end 

allocations.each { |line| 
    puts line 
} 

これは基本的にログを通過し、各割り当て/割り当て解除を取り込み各ペアに固有の値を格納し、それをソートして一致するペアを削除します。残っているものを参照してください。

アップデート:すべての仲介の編集のため申し訳ありませんが(私は行われていた前に、私が誤って掲載)

+0

ご存知のように、あなたはあなたがあなたの質問で話しているalloc/freeトレースを持つログをすでに持っていると仮定しましたが、他の答えはメモリリークを一般的に解決するのに役立ちます。 –

6

私はそれを行う方法が単純である: - マッチングリリース( - ごとのAddRef()レコード呼び出しスタック、 に)それを削除します。 プログラムの終わりにこのようにして、私はAddRefs()をMaching Releaseなしで残しました。ペアを一致させる必要があり、

2

んが私はこの問題を解決するために行っていることは、彼らがデータ構造で操作しますについてできるだけ多くを記録しておいて、このような演算子を削除/ のmalloc /新しい & 自由を上書きすることはありません実行中です。たとえば、malloc/newをオーバーライドすると、呼び出し元のアドレス、要求されたバイト数、返されたポインタ値、シーケンスIDが記録されます。スレッドを扱うかどうかは分かりませんが、スレッドを考慮する必要もあります)。

ルーチンをフリー/削除すると、発信者のアドレスとポインタ情報も記録されます。その後、リストに戻って、malloc/newと一致するようにポインタをキーとして使用します。見つからない場合は、赤い旗を掲げてください。

あなたがそれを買う余裕があるならば、あなたはいつ誰が割り当て呼がなされたかを絶対に確かめるためにシーケンスIDをあなたのデータに埋め込むことができます。ここでの鍵は、可能な限り各取引ペアを一意に識別することです。

次に、各トランザクションを呼び出す関数とともに、メモリ割り当て/解放の履歴を表示する3番目のルーチンがあります。 (これはあなたのリンカーからシンボリックマップを解析することによって達成できます)。どのくらいのメモリを割り当てられたのか、誰がいつ行ったのかを知ることができます。

これらのトランザクションを実行するのに十分なリソースがない場合(通常は8ビットマイクロコントローラの場合)、同じ情報を十分なリソースを持つ別のマシンへのシリアルまたはTCPリンク経由で出力できます。 Windowsの場合

2

を使用すると、Windowsを使っていると言っているので、あなたがマイクロソフトのユーザーモードダンプヒープユーティリティを利用することができるかもしれ、UMDH、付属していましたDebugging Tools for Windows。 UMDHはアプリケーションのメモリ使用量のスナップショットを作成し、各割り当てに使用されるスタックを記録し、複数のスナップショットを比較して、アロケータの「リーク」メモリへの呼び出しを確認します。また、dbghelp.dllを使用してスタックトレースをシンボルに変換します。

「LeakDiag」というUMDHよりも多くのメモリアロケータをサポートする別のMicrosoftツールがありますが、これは見つけにくく、積極的に維持されていないようです。私が正しくリコールすれば、最新のバージョンは少なくとも5歳です。

2

これは漏れを見つけることではありません。スマートポインタの場合、おそらく何千もの時間と呼ばれるCreateObject()のような一般的な場所に向かうでしょう。 ref-countedオブジェクトでコードのどの部分がRelease()を呼び出さなかったのかを判断することです。

1

私はGoogle's Heapcheckerの大きなファンです - それはすべてのリークを捕まえることはありませんが、それらのほとんどを取得します。 (ヒント:すべてのユニットテストにリンクしてください)

4

決定的な方法でリークを再現できるのであれば、よく使う簡単なテクニックは、すべてのスマートポインタの作成順に番号を付けることですコンストラクタ内のカウンタ)、このIDをリークと一緒に報告します。同じIDを持つスマートポインタが構築されると、プログラムを再度実行し、DebugBreak()をトリガします。

また、この素晴らしいツールを検討する必要があります。http://www.codeproject.com/KB/applications/visualleakdetector.aspx

+0

これはスタックダンプと共に行えます。これは、あなたが参照/参照のUIDを介して出力をペアリングすることができます – Ray

4

私は何をしてFUNCTIONLINEのパラメータを取りますクラスでスマートポインタをラップです。コンストラクタが呼び出されるたびにその関数と行のカウントをインクリメントし、デストラクタが呼び出されるたびにカウントを減らします。関数/行/カウント情報をダンプする関数を記述します。あなたの参照の全てがが円形の依存関係とポインタである参照カウントスマートポインタでリークの

+0

私はこの手法を使用しました。すべてがCreateObjから来たら、CreateObjのFUNCTION/LINEの呼び出し元を渡します。 (もちろん、マクロを使ってこれをやっています。あなたが「Who Called Me?」という機能を持っていればさらに優れています) –

+1

@ Krazy/Joe: "Who called called who?" – anand

+1

呼び出し元を特定するのはスタックトレースの最初の部分なので、http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashesではあなたが必要です。 Backtrace()/ execinfo.h。 –

4

参照サイクルを検出するには、参照カウントされたすべてのオブジェクトのグラフが必要です。このようなグラフは構築が容易ではありませんが、実行することは可能です。

リビング参照カウントオブジェクトを登録するには、グローバルset<CRefCounted*>を作成します。これは、オブジェクトの参照カウントが0から1になるときに、thisポインタをセットに追加するだけで、共通のAddRef()実装が簡単になります。同様に、Release()では、参照カウントが1から0になるときにオブジェクトをセットから削除します。

次に、各CRefCounted*から参照オブジェクトのセットを取得する方法をいくつか提供します。それはvirtual set<CRefCounted*> CRefCounted::get_children()または何でもあなたに合っているかもしれません。今、グラフを歩く方法があります。

最後に、cycle detection in a directed graphのお気に入りのアルゴリズムを実装します。プログラムを起動し、いくつかのサイクルを作成し、サイクル検出器を実行します。楽しい! :)

+0

私はあなたの考え方が気に入っていますが、オブジェクトから参照されるオブジェクトを取得する簡単な方法はないと思います:get_children –

+0

@lzprgmrはい、 'get_children()'は、クラスが保持する参照の知識。それは私が「構築するのが簡単ではない」という意味です。 – Constantin

0

最初のステップは、どのクラスが漏れているかを知ることです。 1.参照を増やしている人を見つけることができます。 1. shared_ptrでラップされたクラスのコンストラクターにブレークポイントを設定します。 2.参照カウントを大きくすると、shared_ptr内のデバッガを起動します。変数pn-> pi _-> use_count_ 式を評価することによってその変数のアドレスを取得します(& this-> pn-> pi_)。ビジュアルスタジオデバッガで、[デバッグ] - > [新規ブレークポイント] - > [新規データブレークポイント]を選択します。 変数のアドレスを入力します。 4.プログラムを実行します。コード内のある点が増加しているときはいつでも、あなたのプログラムは停止し、参照カウンターを減少させます。 これらが一致しているかどうかを確認する必要があります。

関連する問題