-->

Detecting memory leak in reference counted objects

2019-04-29 02:17发布

问题:

I am trying to print on which line addref and release is called.Here is code

In code below I have created on ReferenceCount class whose main functionality to increase and decrease refernce count. Referencemanager class keeps track of reference count and deletes the object once it reaches 0.

Test1 is test class .In main I am creating Test1 pointer and wrapping it with CReferenceManager class. Now during creation of CReferenceManager class AddRef is called and while destruction Release would be called.

If there is memory leak then it would be easier to detect if I can print out FILE and LINE numbers when AddRef and Release called with reference counts at that point.

If there a way that I can print FILE and LINE number from where AddRef and Release gets called. One way is that I can overwrite AddRef and Release in derived classes and prinf FILE and LINE numbers

//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);

}

Similare questions I have posted finding who creates object via smart pointer Design pattern to detect memory leaks for reference counted smart pointers

but non of the answers give right explanation to tackle this proble,

回答1:

The only way is to define macros for calling AddRef and Release, since there is no way for the functions to know internally from where they are being called. So you could use something like.

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

Also, different compilers have different pre-defined macros; if portability is a concern, it's something you should look into when writing code like the above. MSDN reference (2003)

Given your comments below, i might offer another somewhat hackish solution. You may not be able to see where your reference is being released, but you can get more information about where it was created, and which are not being released properly.

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;
}

Note this is just pseudo-code; I doubt it compiles or works out of the box!



回答2:

You should never allocate or release references explicitly in your own code, so storing the source file and line where references are incremented or decremented isn't going to help you at all, since those will (should!) always be inside the reference counting management code.

You did not include the source code to your CReferenceManager class, but based on your description it is a wrapper to a referenced counted object. Is this correct? The correct implementation of this CReferenceManager object should ensure that:

  • a constructor that takes a naked pointer stores the pointer and does not change the reference count (since your CReferenceCount class creates object with one reference)
  • reference is always decremented in the destructor
  • reference is incremented in the copy-constructor
  • reference for the right side object is incremented, and reference for the left side object is decremented in the assignment operator
  • no explicit increment/decrement reference methods should be exposed
  • the operator->() method should return the pointer to the object
  • there should be no direct way to detach the reference counted object from a CReferenceManager instance that owns it. The only way is via assignment of a new reference counted object.

Also, you'd want to make the AddRef() and Release() methods in your CReferenceCount class private, and make them accessible only to the CReferenceManager class via class friendship.

If you follow the above rules in your CReferenceManager class, then you can avoid leaks or other memory problems by ensuring that everybody accesses the object via a CReferenceManager wrapper allocated on the stack. In other words:

To create a new referenced counted object, passed a newly created object (with one reference) to a stack allocated CReferenceManager object. Example:

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

To pass the object as an argument to another function or method, always pass a CReferenceManager object by value (not by reference, and not by pointer). If you do it this way the copy constructor and the destructor will take care of maintaining the reference counts for you. Example:

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);
}

If you need to stick the reference counted object in a container, then insert a CReferenceManager wrapper by value (not its pointer, and not the object's naked pointer). Example:

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!

I believe if you strictly follow the above rules the only problems you will find are bugs in your reference counting implementation.

An example implementation that follows these rules is in this page, though that solution lacks any support for multi-threading protection.

I hope this helps!



回答3:

There is some way of doing this, but first let me ask you one thing. Why you want to manage references by hand and provide an opportunity for memory leaks? you can easily use boost::intrusive_ptr to do the job for you?( if you don't want the boost, there is no problem, see implementation of intrusive_ptr and implement your own class or just copy it to your own file ) and then you don't have a memory leak to search for it!!

But as an answer for your question you could have 2 AddRef/Release one for debug version and another for release and you should add AddRef positions to an structure like std::stack and on Release pop them from stack and at very end you see how much references from witch positions remained in the stack! but if this is for COM implementation remember that COM may call AddRef multiple time and then remove them at later time and thus you can't understand which AddRef have no corresponding Release.



回答4:

For the projects I am involved in I had similar needs. We have our own smart-pointer template class and from time to time memory leaks appeared due to circular references.

To know which smart-pointer referencing a leaked object still is alive (2 or more), we compile the sources with a special pre-processor define which enables special debugging code in the smart-pointer implementation. You can have a look at our smart-pointer class.

In essence, each smart-pointer and reference counted object get a unique id. When we get the id for the leaked object (usually using valgrind to identify the source location of the memory allocation for the leaked object), we use our special debugging code to get all smart-pointer ids which reference the object. Then we use a configuration file where we write down the smart-pointer ids and at next application start-up, this file is read by our debugging tool which then knows for which newly created smart-pointer instance it should trigger a signal for entering the debugger. This reveals the stack trace where that smart-pointer instance was created.

Admittedly, this involves some work and might only pay off for larger projects.

Another possibility would be to record a stack trace inside your AddRef method at runtime. Have a look at my ctkBackTrace class to create a stack trace at runtime. It should be easy to replace the Qt specific types by standard STL types.



回答5:

I guess that with a bit of work and using libunwind you could probably try to get what you need (which would be a really appreciated).

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



回答6:

The principle of reference counting is to increase the counter when the user link to the object and to decrease when they break the link.

So you have to:

  • manipulate smart pointers, not pointers to make increase/decrease transparent
  • overload copy constructor and assign operator of the smart_pointer

Symbolic exemple:

  • A a = new A(); refcount = 0, nobody use it
  • Link<A> lnk( a ); refcount = 1
  • obj.f( lnk ); obj stores lnk, refcount = 2
  • this method may returns since the ownership has been transfered to obj

So, take a look at parameter passing (may do automatic copies) and at copy into foreign objects.

Good tutorials exists on that in the CORBA nebulae.

You may see also ACE or ICE, or 0MQ.



回答7:

One way to do what you asked, is to pass AddRef and Release this information using something like this:

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 ... }

Then when you call the function, you can use a macro similar to what Rollie suggested above, like this:

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

This will pass the file and line where the call is made, which I believe is what you asked for. You can control what you want to do with the information within the methods. Printing them out, as I did above, is just an example. You may want to collect more information beyond this, and log it to another object, so you have a history of your calls, write them to a log file, etc. You may also pass more information from the call points than just the file and line, according to the type and level of tracking you need. The default parameters also allow you to use them without passing anything (by a simple macro redefinition), just to see how the final version will behave, with the overhead of two stack pushes and two condition checks.



回答8:

Short answer: you should use the ideas that others posted, namely making use of ADD/RELEASE macros and passing the predefined __FILE__ and __LINE__ macros that the compiler provides to your tracking class.

Slightly longer answer: You can also use functionality that allows you to walk the stack and see who called the function, which is somewhat more flexible and clean than using macros, but almost certainly slower.

This page shows you how to achieve this when using GCC: http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/.

In Windows you can use some compiler intrinsics along with symbol-lookup functionality. For details check out: http://www.codeproject.com/tools/minidump.asp

Note that in both cases your program would need to include at least some symbols for this to work.

Unless you have special requirements for doing this at runtime, I'd suggest you check out the short answer.