检测引用计数对象中的内存泄漏

时间:2012-10-10 19:28:49

标签: c++ pointers memory-leaks reference-counting

我正在尝试打印调用addref和release的行。这是代码

在下面的代码中,我在ReferenceCount类上创建了其主要功能,以增加和减少引用计数。 Referencemanager类跟踪引用计数,并在对象达到0时删除它。

Test1是测试类。主要是我创建了Test1指针并用CReferenceManager类包装它。现在,在创建CReferenceManager类时,将调用AddRef类,同时调用destroy Release。

如果存在内存泄漏,那么当AddRef和Release在此时调用引用计数时,检测是否可以打印出FILE和LINE编号会更容易。

如果有办法我可以从调用AddRef和Release的位置打印FILE和LINE编号。一种方法是我可以在派生类和prinf FILE和LINE编号

中覆盖AddRef和Release
//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问题 finding who creates object via smart pointer Design pattern to detect memory leaks for reference counted smart pointers

但是没有答案给出正确的解释来解决这个问题,

8 个答案:

答案 0 :(得分:6)

唯一的方法是定义用于调用AddRef和Release的宏,因为函数无法从内部知道它们被调用的位置。所以你可以使用类似的东西。

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

此外,不同的编译器具有不同的预定义宏;如果可移植性是一个问题,那么在编写如上所述的代码时应该考虑一下。 MSDN reference (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;
}

注意这只是伪代码;我怀疑它编译或开箱即用!

答案 1 :(得分:5)

你永远不应该在自己的代码中明确地分配或释放引用,因此存储源文件和引用递增或递减的行根本不会对你有帮助,因为那些(应该!)总是在引用计数管理代码。

您没有将源代码包含在CReferenceManager类中,但根据您的描述,它是引用计数对象的包装器。它是否正确?正确实现此CReferenceManager对象应确保:

  • 带有裸指针的构造函数存储指针并且不会更改引用计数(因为您的CReferenceCount类使用一个引用创建对象)
  • 引用总是在析构函数中递减
  • 引用在copy-constructor中递增
  • 右侧对象的引用递增,左侧对象的引用在赋值运算符中递减
  • 不应公开显式增量/减量参考方法
  • operator-&gt;()方法应返回指向对象的指针
  • 应该没有直接的方法将引用计数对象从拥有它的CReferenceManager实例中分离出来。唯一的方法是通过分配新的引用计数对象。

此外,您希望将CReferenceCount类中的AddRef()Release()方法设为私有,并且只能通过类友谊访问CReferenceManager类。

如果您在CReferenceManager类中遵循上述规则,那么您可以通过确保每个人通过堆栈上分配的CReferenceManager包装器访问对象来避免泄漏或其他内存问题。换句话说:

要创建新引用的计数对象,请将新创建的对象(带有一个引用)传递给堆栈分配的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!

我相信如果你严格遵守上述规则,你会发现的唯一问题是你的引用计数实现中的错误。

遵循这些规则的示例实现位于this page,但该解决方案缺乏对多线程保护的支持。

我希望这有帮助!

答案 2 :(得分:3)

有一些方法可以做到这一点,但首先让我问你一件事。为什么要手动管理引用并提供内存泄漏的机会?您可以轻松使用boost::intrusive_ptr为您完成工作吗?(如果您不想要提升,则没有问题,请参阅intrusive_ptr的实施并实施您自己的课程或只是将其复制到您的自己的文件)然后你没有内存泄漏来搜索它!!

但作为您问题的答案,您可以将2 AddRef/Release用于调试版,另一个用于发布,您应该将AddRef个位置添加到std::stack和{{{{{ 1}}从Release弹出它们,最后你会看到有多少来自女巫位置的引用仍在堆栈中!但如果这是用于COM实现,请记住COM可能会多次调用stack,然后在以后删除它们,因此您无法理解哪个AddRef没有相应的AddRef

答案 3 :(得分:2)

对于我参与的项目,我有类似的需求。我们有自己的智能指针模板类,并且由于循环引用而不时出现内存泄漏。

要知道哪个引用泄漏对象的智能指针仍处于活动状态(2个或更多),我们使用特殊的预处理器定义编译源代码,这将在智能指针实现中启用特殊的调试代码。您可以查看我们的smart-pointer class

实质上,每个智能指针和引用计数对象都获得唯一的ID。当我们获取泄漏对象的id(通常使用valgrind来识别泄漏对象的内存分配的源位置)时,我们使用我们的特殊调试代码来获取引用该对象的所有智能指针ID。然后我们使用一个配置文件来写下智能指针ID,在下一次应用程序启动时,我们的调试工具会读取这个文件,然后知道新创建的智能指针实例应该触发输入的信号。调试器。这揭示了创建智能指针实例的堆栈跟踪。

不可否认,这涉及一些工作,可能只会为更大的项目带来回报。

另一种可能性是在运行时在AddRef方法内记录堆栈跟踪。查看我的ctkBackTrace类以在运行时创建堆栈跟踪。应该很容易用标准STL类型替换Qt特定类型。

答案 4 :(得分:1)

我想通过一些工作并使用 libunwind 你可能会尝试得到你需要的东西(这是非常感激的)。

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

答案 5 :(得分:1)

引用计数的原则是当用户链接到对象时增加计数器,并在用户断开链接时减少计数器。

所以你必须:

  • 操纵智能指针,而不是使增加/减少透明的指针
  • 重载复制构造函数和指定smart_pointer的运算符

象征性例子:

  • A a = new A(); refcount = 0,没有人使用它
  • Link<A> lnk( a ); refcount = 1
  • obj.f( lnk ); obj存储lnk,refcount = 2
  • 此方法可能会返回,因为所有权已转移到obj

因此,看一下参数传递(可以自动复制)和复制到外来对象。

CORBA星云中存在很好的教程。

您还可以看到ACEICE0MQ

答案 6 :(得分: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__)

这会传递调用的文件和行,我相信这就是你要求的。您可以使用方法中的信息控制要执行的操作。正如我上面所做的那样打印它们只是一个例子。您可能希望收集更多信息,并将其记录到另一个对象,这样您就可以记录您的呼叫,将它们写入日志文件等。您还可以从呼叫点传递更多信息,而不仅仅是文件和线,根据您需要的跟踪类型和级别。默认参数还允许您在不传递任何内容的情况下使用它们(通过简单的宏重新定义),只是为了查看最终版本的行为,以及两次堆栈推送和两次条件检查的开销。

答案 7 :(得分: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

请注意,在这两种情况下,您的程序都需要包含至少一些符号才能使其正常工作。

除非您在运行时对此有特殊要求,否则我建议您查看简短的答案。