6

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

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

Test1 是测试类。我主要创建 Test1 指针并用 CReferenceManager 类包装它。现在在创建 CReferenceManager 类时调用 AddRef 并在销毁时调用 Release。

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

如果有一种方法可以打印调用 AddRef 和 Release 的 FILE 和 LINE 编号。一种方法是我可以覆盖派生类中的 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 类创建具有一个引用的对象)
  • 引用总是在析构函数中递减
  • 引用在复制构造函数中递增
  • 在赋值运算符中,右侧对象的引用递增,左侧对象的引用递减
  • 不应公开显式递增/递减参考方法
  • operator->() 方法应该返回指向对象的指针
  • 应该没有直接的方法可以将引用计数对象从拥有它的 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!

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

遵循这些规则的示例实现在此页面中,尽管该解决方案缺乏对多线程保护的任何支持。

我希望这有帮助!

于 2012-10-20T17:26:50.430 回答
3

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

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

于 2012-10-10T20:39:10.607 回答
2

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

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

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

诚然,这涉及一些工作,并且可能只会为较大的项目带来回报。

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

于 2012-10-21T16:19:50.530 回答
1

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.

于 2012-10-21T09:14:22.943 回答
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 回答
1

做你问的一种方法是通过 AddRef 和 Release 这个信息使用这样的东西:

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__)

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

于 2012-10-21T17:28:05.960 回答
1

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

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

于 2012-10-19T08:52:04.307 回答