6

addrefとreleaseが呼び出される行を印刷しようとしています。コードは次のとおりです。

以下のコードでは、参照数を増減する主な機能を持つReferenceCountクラスを作成しました。Referencemanagerクラスは、参照カウントを追跡し、オブジェクトが0に達するとオブジェクトを削除します。

Test1はテストクラスです。主に、Test1ポインターを作成し、CReferenceManagerクラスでラップしています。これで、CReferenceManagerクラスの作成中にAddRefが呼び出され、破棄中にReleaseが呼び出されます。

メモリリークがある場合は、AddRefとReleaseがその時点で参照カウントを使用して呼び出されたときに、FILE番号とLINE番号を出力できるかどうかを簡単に検出できます。

AddRefとReleaseが呼び出された場所からFILE番号とLINE番号を出力できる方法がある場合。1つの方法は、派生クラスのAddRefとRelease、およびprinfのFILE番号とLINE番号を上書きできることです。

//ReferenceCount.h
#include <string>
#include <Windows.h>

using namespace std;
class CReferenceCount
{
public:
   CReferenceCount();
   virtual ~CReferenceCount();
   virtual void AddRef();
   virtual bool Release();


private:
   LONG m_ref;

};


// RefCount.cpp 
//

#include "stdafx.h"
#include "ReferenceCount.h"


CReferenceCount::CReferenceCount():m_ref(0)
{
   AddRef();

}

CReferenceCount::~CReferenceCount()
{
}

void CReferenceCount::AddRef()
{
    InterlockedIncrement(&m_ref);
}

bool CReferenceCount::Release()
{
   if (InterlockedDecrement(&m_ref) == 0)
   {
      delete this;
      return true;
   }

   return false;
}



//ReferenceManager.h
#include <string>
#include <Windows.h>

using namespace std;
class CReferenceCount
{
public:
   CReferenceCount();
   virtual ~CReferenceCount();
   virtual void AddRef();
   virtual bool Release();


private:
   LONG m_ref;

};

//test.cpp
#include "stdafx.h"
#include "ReferenceCount.h"
#include "RefManager.h"
#include <iostream>
using namespace std;

class Test1: public CReferenceCount
{
public:
    Test1(){}
    ~Test1(){}

private :
    int m_i;
};

void main()
{
    Test1 *pTest= new Test1();
    CReferenceManager<Test1> testRef(pTest);

}

参照カウントされたスマートポインターのメモリリークを検出するためのスマートポインターデザインパターンを介してオブジェクトを作成する人を見つけることを 投稿した同様の質問

しかし、答えのどれもこの問題に取り組むための正しい説明を与えません、

4

8 に答える 8

6

唯一の方法は、AddRef と Release を呼び出すためのマクロを定義することです。これは、関数がどこから呼び出されているかを内部的に知る方法がないためです。したがって、次のようなものを使用できます。

#define RELEASE(obj) cout << __LINE__ << ":" << __FILE__ << endl; (obj).Release();

また、異なるコンパイラには異なる定義済みマクロがあります。移植性が懸念される場合は、上記のようなコードを記述するときに検討する必要があります。MSDN リファレンス (2003)

以下のコメントを踏まえて、別のややハックなソリューションを提供するかもしれません。参照がどこでリリースされているかを確認できない場合がありますが、参照が作成された場所と適切にリリースされていない場所に関する詳細情報を取得できます。

template <typename T>
struct CReferenceManager
{
    CReferenceManager(const T & _obj, const string & _file, int _line) : mObj(_obj), mFile(_file), mLine(_line)
    {
        cout << "Constructing from " << _file << ":" << _line << endl;
        CReferenceManager::sObjects[make_pair(mFile, mLine)]++;
        mObj.addRef();
    }

    ~CReferenceManager()
    {
        cout << "Destructing object created at " << mFile << ":" << mLine << endl;
        CReferenceManager::sObjects[make_pair(mFile, mLine)]--;
        mObj.Release();
    }

    static map<pair<string, int>, int> sObjects;
    string mFile;
    int mLine;
    T obj;
}

int main()
{
...
    // Cycle through sObjects before return, note any unreleased entries
    return 0;
}

これは単なる疑似コードであることに注意してください。すぐにコンパイルまたは動作するのではないかと思います!

于 2012-10-10T19:39:42.453 に答える
5

独自のコードで明示的に参照を割り当てたり解放したりしないでください。そのため、参照が増加または減少するソース ファイルと行を保存しても、まったく役に立ちません。コード。

ソースコードを CReferenceManager クラスに含めませんでしたが、説明に基づいて、参照されたカウントされたオブジェクトへのラッパーです。これは正しいです?この CReferenceManager オブジェクトを正しく実装すると、次のことが保証されます。

  • ネイキッド ポインターを受け取るコンストラクターはポインターを格納し、参照カウントを変更しません (CReferenceCount クラスは 1 つの参照でオブジェクトを作成するため)。
  • 参照はデストラクタで常にデクリメントされます
  • 参照はコピーコンストラクターでインクリメントされます
  • 右側のオブジェクトの参照がインクリメントされ、左側のオブジェクトの参照が代入演算子でデクリメントされます
  • 明示的なインクリメント/デクリメント参照メソッドを公開しないでください
  • operator->() メソッドはオブジェクトへのポインターを返す必要があります
  • それを所有する CReferenceManager インスタンスから参照カウント オブジェクトをデタッチする直接的な方法はありません。唯一の方法は、新しい参照カウント オブジェクトを割り当てることです。

また、CReferenceCount クラスのメソッドAddRef()Release()メソッドを非公開にし、クラス フレンドシップを介して CReferenceManager クラスだけがアクセスできるようにします。

CReferenceManager クラスで上記のルールに従う場合、スタックに割り当てられた CReferenceManager ラッパーを介して全員がオブジェクトにアクセスできるようにすることで、リークやその他のメモリの問題を回避できます。言い換えると:

新しい参照カウント オブジェクトを作成するには、新しく作成されたオブジェクト (1 つの参照を含む) を、スタックに割り当てられた CReferenceManager オブジェクトに渡しました。例:

CReferenceManager<Test1> testRef(new Test1());

オブジェクトを引数として別の関数またはメソッドに渡すには、常に CReferenceManager オブジェクトを値で渡します (参照ではなく、ポインターではありません)。このようにすると、コピー コンストラクターとデストラクタが参照カウントを維持します。例:

void someFunction(CReferenceManager<Test1> testObj)
{
    // use testObj as if it was a naked pointer
    // reference mananagement is automatically handled
    printf("some value: %d\n", testObj->someValue());
}

int main()
{
    CReferenceManager<Test1> testRef(new Test1());
    someFunction(testRef);
}

参照カウント オブジェクトをコンテナーに貼り付ける必要がある場合は、CReferenceManager ラッパーを値 (そのポインターではなく、オブジェクトのネイキッド ポインターではなく) で挿入します。例:

std::vector< CReferenceManager<Test1> > myVector;
CReferenceManager<Test1> testRef(new Test1());
myVector.push_back(testRef);
myVector[0]->some_method(); // invoke the object as if it was a pointer!

上記のルールを厳密に守れば、参照カウントの実装のバグだけが見つかると思います。

これらのルールに従う実装例はこのページにありますが、そのソリューションではマルチスレッド保護がサポートされていません。

これが役立つことを願っています!

于 2012-10-20T17:26:50.430 に答える
3

これにはいくつかの方法がありますが、最初に 1 つだけ質問させてください。手動で参照を管理し、メモリ リークの機会を提供する必要があるのはなぜですか? あなたは簡単にあなたのために仕事をするために使うことができますboost::intrusive_ptrか?intrusive_ptrそれを検索するためのメモリリーク!!

しかし、あなたの質問への答えとして、AddRef/Releaseデバッグ バージョン用とリリース用の 2 つを持つことができますAddRef。構造体に位置を追加する必要があり、それらstd::stackReleaseポップするstackと、最後に魔女の位置からの参照がスタックにどれだけ残っているかがわかります! ただし、これが COM 実装の場合は、COM がAddRef複数回呼び出して後で削除する可能性があるAddRefため、対応するRelease.

于 2012-10-10T20:39:10.607 に答える
2

私が関わっているプロジェクトについても、同様のニーズがありました。独自のスマート ポインター テンプレート クラスがあり、循環参照が原因でメモリ リークが発生することがありました。

リークされたオブジェクトを参照するどのスマート ポインターがまだ生きているか (2 つ以上) を知るために、スマート ポインターの実装で特別なデバッグ コードを有効にする特別なプリプロセッサ定義を使用してソースをコンパイルします。私たちのsmart-pointer classを見ることができます。

本質的に、各スマート ポインターと参照カウント オブジェクトは一意の ID を取得します。リークしたオブジェクトの ID を取得すると (通常、valgrind を使用してリークしたオブジェクトのメモリ割り当てのソースの場所を特定します)、特別なデバッグ コードを使用して、オブジェクトを参照するすべてのスマート ポインター ID を取得します。次に、スマート ポインター ID を書き留める構成ファイルを使用します。次のアプリケーションの起動時に、このファイルがデバッグ ツールによって読み取られます。デバッグ ツールは、新しく作成されたどのスマート ポインター インスタンスに対して、入力するための信号をトリガーする必要があるかを認識します。デバッガ。これにより、そのスマート ポインター インスタンスが作成されたスタック トレースが明らかになります。

確かに、これには多少の作業が必要であり、大規模なプロジェクトでのみ報われる可能性があります。

もう 1 つの可能性はAddRef、実行時にメソッド内のスタック トレースを記録することです。ctkBackTraceクラスを見て、実行時にスタック トレースを作成してください。Qt 固有の型を標準の STL 型に置き換えるのは簡単です。

于 2012-10-21T16:19:50.530 に答える
1

要求したことを実行する1つの方法は、AddRefを渡し、次のようなものを使用してこの情報を解放することです。

void CReferenceCount::AddRef(const char *file=0, int line=-1) { if (file) cout << "FILE:" << file; if (line>0) count << " LINE: " << line;   .... do the rest here ... }

次に、関数を呼び出すときに、Rollieが上記で提案したものと同様のマクロを次のように使用できます。

#define ADDREF(x) x.AddRef(__FILE__, __LINE__)

これにより、呼び出しが行われたファイルと行が渡されます。これは、あなたが要求したものであると私は信じています。メソッド内の情報を使用して、実行する操作を制御できます。上で行ったように、それらを印刷することは単なる例です。これ以外の情報を収集して別のオブジェクトに記録すると、通話の履歴を取得したり、ログファイルに書き込んだりすることができます。また、ファイルや必要な追跡のタイプとレベルに応じて、ライン。デフォルトのパラメーターを使用すると、(単純なマクロの再定義によって)何も渡さずにそれらを使用して、2つのスタックプッシュと2つの条件チェックのオーバーヘッドで最終バージョンがどのように動作するかを確認できます。

于 2012-10-21T17:28:05.960 に答える
1

参照カウントの原則は、ユーザーがオブジェクトにリンクするとカウンターを増加させ、リンクを解除すると減少することです。

したがって、次のことを行う必要があります。

  • 増加/減少を透過的にするためのポインタではなく、スマートポインタを操作します
  • smart_pointer のオーバーロード コピー コンストラクターと代入演算子

象徴的な例:

  • A a = new A();refcount = 0、誰も使用しない
  • Link<A> lnk( a );参照カウント = 1
  • obj.f( lnk );obj ストア lnk、refcount = 2
  • 所有権がに譲渡されているため、このメソッドは戻る可能性がありますobj

したがって、パラメーターの受け渡し (自動コピーを行う場合があります) と外部オブジェクトへのコピーを見てください。

CORBA星雲には、それに関する優れたチュートリアルがあります。

ACEまたはICEまたは0MQも表示される場合があります。

于 2012-10-21T09:14:22.943 に答える
1

少し作業してlibunwindを使用すると、おそらく必要なものを取得しようとすることができると思います (これは本当にありがたいことです)。

http://www.nongnu.org/libunwind/docs.html

于 2012-10-19T08:52:04.307 に答える
1

簡単な答え: 他の人が投稿したアイデアを使用する必要があります。つまり、ADD/RELEASE マクロを使用し、コンパイラが追跡クラスに提供する定義済みの __FILE__ および __LINE__ マクロを渡します。

少し長い答え: スタックをたどって関数を呼び出した人を確認できる機能を使用することもできます。これは、マクロを使用するよりも柔軟でクリーンですが、ほぼ確実に遅くなります。

このページでは、GCC を使用してこれを実現する方法を示します: http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/

Windows では、シンボル検索機能と共にいくつかのコンパイラ組み込み関数を使用できます。詳細については、http: //www.codeproject.com/tools/minidump.aspをご覧ください。

どちらの場合も、これを機能させるには、プログラムに少なくともいくつかのシンボルを含める必要があることに注意してください。

実行時にこれを行うための特別な要件がない限り、短い答えを確認することをお勧めします。

于 2012-10-23T00:02:58.567 に答える