查找由智能指针引起的内存泄漏

时间:2008-09-15 21:29:02

标签: c++ pointers memory-leaks smart-pointers

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

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

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

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

开发在 Windows XP 下进行。

(我希望有人理解,我试图解释......)

EDIt:我在谈论循环引用造成的泄漏。

12 个答案:

答案 0 :(得分:16)

请注意,使用引用计数智能指针的一个泄漏源是循环依赖的指针。例如,A有一个指向B的智能指针,B有一个指向A的智能指针.A和B都不会被销毁。你必须找到,然后打破依赖。

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

答案 1 :(得分:6)

我这样做的方式很简单: - 在每个AddRef()记录调用堆栈上, - 匹配Release()将其删除。 这种方式在程序结束时我留下了AddRefs()而没有加工Releases。无需配对,

答案 2 :(得分:4)

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

您还应该考虑这个好工具:http://www.codeproject.com/KB/applications/visualleakdetector.aspx

答案 3 :(得分:4)

我所做的是将智能指针包含一个带有 FUNCTION LINE 参数的类。每次调用构造函数时递增该函数和行的计数,并在每次调用析构函数时递减计数。然后,编写一个转储函数/行/计数信息的函数。这告诉您创建所有引用的位置

答案 4 :(得分:4)

要检测参考周期,您需要获得所有参考计数对象的图表。这样的图表不容易构建,但可以完成。

创建一个全局set<CRefCounted*>来注册生活引用计数对象。如果你有共同的AddRef()实现,这会更容易 - 当对象的引用计数从0变为1时,只需向该集添加this指针。同样,在Release()中,当引用计数从1到0。

接下来,提供一些方法来从每个CRefCounted*获取一组引用的对象。它可能是virtual set<CRefCounted*> CRefCounted::get_children()或任何适合你的。现在你有办法走图表。

最后,为cycle detection in a directed graph实施您最喜欢的算法。启动程序,创建一些循环并运行循环检测器。请享用! :)

答案 5 :(得分:2)

我要做的就是覆盖 malloc / new &amp; 免费/删除运营商,以便他们尽可能多地跟踪您正在执行的操作的数据结构。

例如,当覆盖 malloc / new 时,您可以创建调用者地址,请求的字节数,返回的指定指针值以及序列ID的记录,以便所有记录都可以排序(我不知道你是否处理线程,但你也需要考虑到这一点)。

在编写 free / delete 例程时,我还会跟踪调用者的地址和指针信息。然后我回顾列表并尝试使用指针作为我的键来匹配 malloc / new 对应物。如果我找不到它,请举起一面红旗。

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

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

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

答案 6 :(得分:2)

由于您说您使用的是Windows,因此您可以利用Microsoft UMDH附带的Microsoft用户模式转储堆实用程序Debugging Tools for Windows。 UMDH为应用程序的内存使用情况创建快照,记录用于每个分配的堆栈,并允许您比较多个快照以查看对分配器“泄漏”内存的哪些调用。它还使用dbghelp.dll将堆栈跟踪转换为符号。

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

答案 7 :(得分:2)

这不是发现泄漏的问题。在智能指针的情况下,它很可能直接指向像CreateObject()这样的通用位置,这被称为数千次。这是确定代码中的哪个位置没有在ref-counting对象上调用Release()的问题。

答案 8 :(得分:1)

如果我是你,我会把日志写成一个快速的脚本来做类似下面的事情(我的是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
}

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

更新:抱歉所有中间修改(我在完成之前意外发布)

答案 9 :(得分:1)

对于Windows,请查看:

MFC Memory Leak Detection

答案 10 :(得分:1)

我是Google's Heapchecker的忠实粉丝 - 它不会捕获所有泄漏,但它会获得大部分泄漏。 (提示:将其链接到您的所有单元测试中。)

答案 11 :(得分:0)

第一步可能是知道哪个类正在泄漏。 一旦你知道它,你就可以找到谁在增加参考: 1.在由shared_ptr包装的类的构造函数上放置一个断点。 2.当增加引用计数时,在shared_ptr中调试调试器:查看变量pn-&gt; pi _-&gt; use_count_ 通过评估表达式获取该变量的地址(类似于:&amp; this-&gt; pn-&gt; pi_.use_count_),您将获得一个地址 3.在visual studio调试器中,转到Debug-&gt; New Breakpoint-&gt; New Data Breakpoint ... 输入变量的地址 4.运行程序。每当代码中的某些点增加并减少参考计数器时,您的程序就会停止。 然后你需要检查它们是否匹配。