2012-10-10 242 views
6

我想打印在哪一行AddRef和Release是called.Here是代码检测内存泄漏计算对象

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

Test1是测试类。主要我创建Test1指针并用CReferenceManager类包装它。现在在创建CReferenceManager类的过程中,AddRef被调用,而销毁Release将被调用。

如果存在内存泄漏,那么当AddRef和Release在该点引用计数时可以打印出FILE和LINE数字将更容易检测。

如果有方法可以打印AddRef和Release被调用的FILE和LINE号码。一种方法是,我可以覆盖AddRef和在派生​​类和prinf文件和行号

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

但非的答案给解决这个正确的解释有问题,

+9

'删除此;'OMG !!! –

+0

你使用智能指针调用AddRef/Release还是你手动调用它们?如果你手动给他们打电话,我强烈反对这个建议。 –

+0

模板将是更好的解决方案;可以使模板适合每个对象,就像一个std :: shared_ptr –

回答

6

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

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

另外,不同的编译器有不同的预定义的宏;如果可移植性是一个问题,那么在编写如上所述的代码时应该考虑一下。 MSDN reference (2003)

鉴于您的评论如下,我可能会提供另一个有点hackish的解决方案。您可能无法看到引用的发布位置,但您可以获取有关它的创建位置以及哪些未正确发布的更多信息。

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

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

+0

这是我在这里建议,昨天:http://stackoverflow.com/a/12806087/241536 –

+0

或多或少.. –

+0

如果我使用这个宏,它将打印FILE作为RefManager,而不是test.cpp.My,意图是打印test.cpp和它的行号,从Release中调用。 – anand

2

有一些这样做的方法,但首先让我问你一件事。为什么你想要手动管理引用并提供内存泄漏的机会?你可以很容易地使用boost::intrusive_ptr为你完成这项工作(如果你不想提升,没有问题,请参阅intrusive_ptr的实现,实现你自己的类或者将它复制到你自己的文件中)没有内存泄漏来搜索它!

至于你的问题的答案,你可以有2 AddRef/Release一个用于调试版本,另一个版本,你应该添加AddRef位置的结构是怎样的std::stackReleasestack弹出他们,并在结尾处,您看到多少女巫职位的参考资料仍保留在堆叠中!但是如果这是为COM实现记住COM可能会多次调用AddRef,然后在以后删除它们,因此您无法了解哪个AddRef没有对应的Release

5

你不应该分配或在自己的代码显式释放的引用,因此存储,但均递增或递减是不会帮你在所有的源文件和行,因为这些会(应!)始终在参考计数管理代码内。

您没有将源代码包含到您的CReferenceManager类中,但根据您的描述,它是引用计数对象的包装。它是否正确?正确执行这一CReferenceManager对象应确保:

  • 一个构造函数裸指针存储指针,并且不更改引用计数(因为你CReferenceCount类一个参考创建对象)
  • 引用总是递减在析构函数
  • 参考在拷贝构造
  • 用于右侧对象引用被递增递增,并且用于左侧的对象引用在赋值运算符被递减
  • 没有明确的增量/减量的参考方法应该暴露
  • 操作 - >()方法应该将指针返回到对象
  • 应该有分离从拥有它CReferenceManager实例的引用计数对象没有直接的方法。唯一的方法是通过分配一个新的引用计数对象。

而且,你想使AddRef()Release()方法在CReferenceCount类私人,使他们只能访问通过类友谊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,尽管该解决方案缺少对多线程保护的任何支持。

我希望这有助于!

1

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

所以,你必须:

  • 操纵智能指针,不是指针,使增大/减小透明
  • 超载拷贝构造函数和分配smart_pointer的操作

符号为例:

  • A a = new A(); refcount = 0,没有人使用它
  • Link<A> lnk(a);的refcount = 1个
  • obj.f(lnk); OBJ存储LNK,引用次数= 2
  • 这种方法可能由于所有权返回已被转移到obj

所以,看看参数传递(可以做自动复制)并复制到异物中。

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

您可能也会看到ACEICE0MQ

2

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

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

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

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

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

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

然后当你调用该函数,你可以使用宏类似于罗利以上建议,像这样:

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

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

+0

赞同在Rollie的回答中指出的,因为OP需要将宏调用放入他的CReferenceManager模板类中,所以这将不起作用。所以宏将始终打印RefManager作为文件名。 – Sascha

+0

嗯,没有。如果宏在头文件中,它就会工作,并且它在其他几个cpp文件中使用。 – DNT

+0

这里的空间不足以粘贴示例运行,但如果有人感兴趣,我可以添加一个答案。 – DNT

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

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

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