19

スマートポインタによって引き起こされるメモリリークを発見する「テクニック」を知っている人はいますか? 私は現在、C++で書かれた大規模なプロジェクトに取り組んでおり、参照カウントを伴うスマート ポインターを多用しています。明らかに、コードのどこかでまだ参照されているスマート ポインターによって引き起こされるメモリ リークがいくつかあるため、それらのメモリは解放されません。「不必要な」参照を含むコード行を見つけるのは非常に困難です。これにより、対応するオブジェクトが解放されなくなります (ただし、それはもはや役に立ちません)。

Web で、参照カウンターのインクリメント/デクリメント操作のコール スタックを収集することを提案するアドバイスを見つけました。これにより、どのコードが参照カウンターの増加または減少を引き起こしたのか、良いヒントが得られます。

しかし、私が必要としているのは、対応する「呼び出しスタックの増加/減少」をグループ化するある種のアルゴリズムです。これらのコール スタックのペアを削除した後、(少なくとも) 1 つの「コール スタックの増加」が残っていることを願っています。これで、リークを修正することは大したことではなくなります。

しかし、グループ化を行う「アルゴリズム」のアイデアはありますか?

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

(私が説明しようとしたことを誰かが理解してくれることを願っています...)

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

4

12 に答える 12

18

参照カウント スマート ポインターでのリークの原因の 1 つは、循環依存関係を持つポインターであることに注意してください。たとえば、A には B へのスマート ポインターがあり、B には A へのスマート ポインターがあります。A も B も破棄されません。依存関係を見つけてから壊す必要があります。

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

于 2008-09-17T00:09:19.523 に答える
7

私がそれを行う方法は簡単です: - すべての AddRef() レコードの呼び出しスタックで、 - 一致する Release() はそれを削除します。このようにして、プログラムの最後に、Release を照合せずに AddRefs() を残します。ペアを一致させる必要はありません。

于 2008-09-15T21:45:53.120 に答える
4

決定論的な方法でリークを再現できる場合、私がよく使用する簡単な手法は、すべてのスマート ポインターに構築順に番号を付け (コンストラクターで静的カウンターを使用)、この ID をリークと共に報告することです。次に、プログラムを再度実行し、同じ ID を持つスマート ポインターが構築されたときに DebugBreak() をトリガーします。

この優れたツールも検討する必要があります: http://www.codeproject.com/KB/applications/visualleakdetector.aspx

于 2008-09-16T14:27:52.290 に答える
4

私がやっていることは、スマート ポインターを、FUNCTIONパラメーターとLINEパラメーターを受け取るクラスでラップすることです。コンストラクタが呼び出されるたびにその関数と行のカウントを増やし、デストラクタが呼び出されるたびにカウントを減らします。次に、関数/行/カウント情報をダンプする関数を作成します。これにより、すべての参照が作成された場所がわかります

于 2008-09-16T14:45:50.437 に答える
4

参照サイクルを検出するには、参照カウントされたすべてのオブジェクトのグラフが必要です。このようなグラフを作成するのは簡単ではありませんが、作成することはできます。

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

次に、各 から参照オブジェクトのセットを取得する何らかの方法を提供しますCRefCounted*。それは、virtual set<CRefCounted*> CRefCounted::get_children()またはあなたに合ったものである可能性があります。これで、グラフをたどる方法ができました。

最後に、有向グラフでサイクル検出用の好みのアルゴリズムを実装します。プログラムを開始し、いくつかのサイクルを作成し、サイクル検出器を実行します。楽しみ!:)

于 2008-10-09T21:44:00.097 に答える
2

漏れを見つけることは問題ではありません。スマートポインタの場合、おそらく何千回も呼び出されているCreateObject()のような一般的な場所に直接接続されます。コード内のどの場所で参照カウントオブジェクトのRelease()が呼び出されなかったかを判断する必要があります。

于 2008-09-16T07:24:41.387 に答える
2

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

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

于 2008-09-16T02:21:22.380 に答える
2

これを解決するために私が行ったことは、malloc/new & free/delete演算子をオーバーライドして、実行中の操作について可能な限りデータ構造を追跡できるようにすることです。

たとえば、malloc/newをオーバーライドする場合、呼び出し元のアドレス、要求されたバイト数、返された割り当てられたポインター値、およびシーケンス ID のレコードを作成して、すべてのレコードをシーケンスできるようにすることができます (処理するかどうかはわかりません)。スレッドですが、それも考慮する必要があります)。

free/deleteルーチンを作成するときは、呼び出し元のアドレスとポインター情報も追跡します。次に、リストをさかのぼって調べ、ポインターをキーとして使用して、対応するmalloc/newを一致させようとします。見つからない場合は、危険信号を上げてください。

余裕がある場合は、シーケンス ID をデータに埋め込んで、誰がいつ割り当て呼び出しを行ったかを完全に確認できます。ここで重要なのは、各トランザクション ペアをできる限り一意に識別することです。

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

これらのトランザクションを実行するのに十分なリソースがない場合 (私の典型的な 8 ビット マイクロコントローラーのケース)、シリアルまたは TCP リンクを介して、十分なリソースがある別のマシンに同じ情報を出力できます。

于 2008-09-15T21:53:14.240 に答える
1

Windowsの場合は、以下を確認してください。

MFCメモリリークの検出

于 2008-09-15T22:26:24.817 に答える
1

私はGoogle の Heapchecker の大ファンです。すべてのリークを検出するわけではありませんが、ほとんどのリークを検出できます。(ヒント: すべてのユニットテストにリンクしてください。)

于 2008-09-16T09:21:51.217 に答える
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
}

これは基本的にログを通過し、各割り当て/割り当て解除をキャプチャし、各ペアの一意の値を保存してから、並べ替えて一致するペアを削除し、何が残っているかを確認します。

更新: すべての中間編集について申し訳ありません (完了する前に誤って投稿してしまいました)。

于 2008-09-15T21:37:04.607 に答える
0

最初のステップは、どのクラスがリークしているかを知ることです。それがわかれば、誰が参照を増やしているかがわかります。 1. shared_ptr によってラップされているクラスのコンストラクターにブレークポイントを置きます。2. 参照カウントを増やすときに、shared_ptr 内のデバッガーでステップ インします。変数 pn->pi_->use_count_ を調べます。アドレスを取得します。 3. Visual Studio デバッガーで、[デバッグ] -> [新しいブレークポイント] -> [新しいデータ ブレークポイント] に移動します。変数のアドレスを入力します。 4. プログラムを実行します。コードのあるポイントで参照カウンターが増減するたびに、プログラムは停止します。次に、それらが一致しているかどうかを確認する必要があります。

于 2013-03-21T11:51:22.417 に答える