有人知道一种“技术”来发现智能指针引起的内存泄漏吗?我目前正在开发一个用以下语言编写的大型项目 C++ 大量使用带有引用计数的智能指针。显然,我们有一些由智能指针引起的内存泄漏,这些内存泄漏仍然在代码中的某个地方被引用,因此它们的内存没有被释放。很难找到带有“不必要的”引用的代码行,这会导致相应的对象不被释放(尽管它不再使用)。

我在网上找到了一些建议,建议收集引用计数器的递增/递减操作的调用堆栈。这给了我一个很好的提示,哪一段代码导致引用计数器增加或减少。

但我需要的是某种将相应的“增加/减少调用堆栈”分组在一起的算法。删除这些对调用堆栈后,我希望(至少)留下一个“增加调用堆栈”,它向我显示了带有“不必要”引用的代码段,导致相应的对象未被释放。现在修复泄漏就不是什么大问题了!

但是有人对进行分组的“算法”有想法吗?

开发发生在 视窗XP.

(我希望有人理解我试图解释的内容......)

编辑:我说的是循环引用引起的泄漏。

有帮助吗?

解决方案

请注意,泄漏源之一是 引用计数智能指针 是指针 循环依赖. 。例如,A有一个指向B的智能指针,B有一个指向A的智能指针。A 和 B 都不会被破坏。您必须找到并打破依赖关系。

如果可能,请使用 boost 智能指针,并为应该是数据所有者的指针使用shared_ptr,为不应该调用delete的指针使用weak_ptr。

其他提示

我的做法很简单:- 在每个addRef()记录呼叫堆栈上, - 匹配版本()将其删除。这样,在程序结束时,我只剩下 AddRefs() 而不处理发布。无需配对,

如果您能够以确定性的方式重现泄漏,我经常使用的一种简单技术是按照构造顺序对所有智能指针进行编号(在构造函数中使用静态计数器),并将此 ID 与泄漏一起报告。然后再次运行程序,并在构造具有相同ID的智能指针时触发DebugBreak()。

您还应该考虑这个很棒的工具: http://www.codeproject.com/KB/applications/VisualleakDetector.aspx

要检测引用循环,您需要拥有所有引用计数对象的图表。这样的图不容易构造,但可以做到。

创建一个全球 set<CRefCounted*> 注册活的引用计数对象。如果您有通用的 AddRef() 实现,这会更容易 - 只需添加 this 当对象的引用计数从 0 变为 1 时,指向集合的指针。类似地,在 Release() 中,当对象的引用计数从 1 变为 0 时,将对象从集合中删除。

接下来,提供一些方法来从每个对象中获取引用的对象集 CRefCounted*. 。它可能是一个 virtual set<CRefCounted*> CRefCounted::get_children() 或者任何适合你的东西。现在您有了一种遍历图表的方法。

最后,实现你最喜欢的算法 有向图中的循环检测. 。启动程序,创建一些循环并运行循环检测器。享受!:)

我所做的就是用一个类包装智能指针,该类需要 功能线 参数。每次调用构造函数时增加该函数和行的计数,并在每次调用析构函数时减少该计数。然后,编写一个转储函数/行/计数信息的函数。这会告诉您所有参考文献的创建位置

我为解决这个问题所做的就是覆盖 分配/新建 & 释放/删除 操作符,以便它们在数据结构中尽可能多地跟踪您正在执行的操作。

例如,当覆盖 分配/新建, ,您可以创建调用者地址、请求的字节数、返回的指定指针值和序列 ID 的记录,以便对所有记录进行排序(我不知道您是否处理线程,但您需要将其纳入帐户也)。

当写下 释放/删除 例程中,我还跟踪调用者的地址和指针信息。然后我向后查看列表并尝试匹配 分配/新建 使用指针作为我的密钥的对应部分。如果我找不到它,请举起红旗。

如果您负担得起,您可以在数据中嵌入序列 ID,以绝对确定谁以及何时进行了分配调用。这里的关键是尽可能唯一地标识每个交易对。

然后,您将有第三个例程,显示内存分配/释放历史记录,以及调用每个事务的函数。(这可以通过解析链接器中的符号映射来完成)。您随时都会知道您将分配多少内存以及是谁分配的。

如果您没有足够的资源来执行这些事务(我的典型情况是 8 位微控制器),您可以通过串行或 TCP 链接将相同的信息输出到另一台具有足够资源的计算机。

这不是找漏洞的问题。对于智能指针,它很可能会定向到一些通用位置,例如 CreateObject(),它被调用了数千次。这是确定代码中的哪个位置没有对引用计数对象调用 Release() 的问题。

既然您说您使用的是 Windows,那么您也许可以利用 Microsoft 的用户模式转储堆实用程序, UMDH, ,它带有 Windows 调试工具. 。UMDH 制作应用程序内存使用情况的快照,记录每次分配使用的堆栈,并允许您比较多个快照以查看哪些对分配器的调用“泄漏”了内存。它还使用 dbghelp.dll 将堆栈跟踪转换为符号。

还有另一个名为“LeakDiag”的 Microsoft 工具,它支持比 UMDH 更多的内存分配器,但它更难找到,并且似乎没有得到积极维护。如果我没记错的话,最新版本至少已经有五年了。

如果我是你,我会获取日志并编写一个快速脚本来执行如下操作(我的是 Ruby):

def allocation?(line)
  # determine if this line is a log line indicating allocation/deallocation
end

def unique_stack(line)
  # return a string that is equal for pairs of allocation/deallocation
end

allocations = []
file = File.new "the-log.log"
file.each_line { |line|
  # custom function to determine if line is an alloc/dealloc
  if allocation? line
    # custom function to get unique stack trace where the return value
    # is the same for a alloc and dealloc
    allocations[allocations.length] = unique_stack line
  end
}

allocations.sort!

# go through and remove pairs of allocations that equal,
# ideally 1 will be remaining....
index = 0

while index < allocations.size - 1
  if allocations[index] == allocations[index + 1]
    allocations.delete_at index
  else
    index = index + 1
  end
end

allocations.each { |line|
  puts line
}

这基本上会遍历日志并捕获每个分配/解除分配并为每个对存储一个唯一值,然后对其进行排序并删除匹配的对,看看还剩下什么。

更新:对所有中间编辑表示抱歉(我在完成之前不小心发布了)

对于 Windows,请查看:

MFC内存泄漏检测

我是以下的忠实粉丝 谷歌的堆检查器 ——它不会捕获所有泄漏,但它可以捕获大部分泄漏。(提示:将其链接到您的所有单元测试中。)

第一步可能是知道哪个类正在泄漏。一旦你知道了,你就可以找到谁在增加引用:1.在由shared_ptr 包装的类的构造函数上放置一个断点。2.当共享指针增加引用计数时,使用调试器介入:查看可变pn-> pi _-> use_count_通过评估表达式(这样的事情:&this-> pn-> pi_.use_count_),您将获得一个地址3。在 Visual Studio 调试器中,转到“调试”->“新断点”->“新数据断点...”输入变量4的地址。运行程序。每当代码中的某个点增加和减少引用计数器时,您的程序都会停止。然后您需要检查它们是否匹配。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top