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

时间:2020-03-05 18:54:36  来源:igfitidea点击:

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

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

但是我需要的是一种将相应的"增加/减少调用堆栈"分组在一起的算法。删除了这些成对的调用堆栈之后,我希望(至少)剩下一个"增加调用堆栈",它向我展示了带有"不必要"引用的那段代码,该代码导致相应的对象不被释放。现在,解决泄漏将无济于事!

但是,有人对进行分组的"算法"有想法吗?

开发在Windows XP下进行。

(我希望有人能理解,我想解释的是...)

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

解决方案

回答

如果我是我们,我将记下日志并编写一个快速脚本来执行以下操作(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
}

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

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

回答

我这样做的方法很简单:
在每个AddRef()记录调用堆栈上,
匹配的Release()会将其删除。
这样,在程序结束时,我无需处理Release就剩下AddRefs()了。无需配对,

回答

为了解决这个问题,我要做的是重写malloc / new和free / delete运算符,以便它们尽可能跟踪数据结构中我们正在执行的操作。

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

在编写释放/删除例程时,我还会跟踪调用者的地址和指针信息。然后,我向后看一下列表,并尝试使用指针作为我的键来匹配malloc / new。如果找不到,请举一个红旗。

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

然后,我们将拥有第三个例程,该例程显示内存分配/取消分配历史记录以及调用每个事务的功能。 (这可以通过从链接程序中解析符号映射来完成)。我们将随时知道将分配多少内存以及谁分配了。

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

回答

对于Windows,请检出:

MFC内存泄漏检测

回答

由于我们说的是使用Windows,因此我们可以利用Microsoft的用户模式转储堆实用程序UMDH,该实用程序随Windows调试工具一起提供。 UMDH为应用程序的内存使用情况制作快照,记录用于每个分配的堆栈,并让我们比较多个快照以查看对分配器的哪些调用"泄漏"了内存。它还使用dbghelp.dll将堆栈跟踪转换为符号。

还有另一个名为" LeakDiag"的Microsoft工具,它比UMDH支持更多的内存分配器,但是查找起来有点困难,而且似乎没有得到积极维护。如果我没记错的话,最新版本至少有五年的历史。

回答

这不是发现泄漏的问题。在使用智能指针的情况下,很可能会将其定向到诸如CreateObject()之类的通用位置,该位置被调用了数千次。这是确定代码中没有在引用计数的对象上调用Release()的地方的问题。

回答

我是Google Heapchecker的忠实拥护者-它不会捕获所有泄漏,但可以吸收大部分泄漏。 (提示:将其链接到所有单元测试中。)

回答

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

我们还应该考虑使用这个出色的工具:http://www.codeproject.com/KB/applications/visualleakdetector.aspx

回答

我要做的是用带有FUNCTION和LINE参数的类包装智能指针。每次调用构造函数时,都会对该函数和行的计数增加,而每次调用析构函数时,计数的计数都将减小。然后,编写一个转储函数/行/计数信息的函数。告诉我们所有引用的创建位置

回答

请注意,带有引用计数智能指针的泄漏源之一是具有循环依赖关系的指针。例如,A具有指向B的智能指针,而B具有指向A的智能指针。A和B都不会被销毁。我们将必须找到并打破依赖关系。

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

回答

要检测参考周期,我们需要具有所有参考计数对象的图形。这样的图不容易构造,但是可以做到。

创建一个全局的set <CRefCounted *>来注册活动的引用计数对象。如果我们有常见的AddRef()实现,则只需在对象的引用计数从0到1时向集合中添加this指针即可。同样,在Release()中,当引用计数从1到0时将对象从集合中移除。

接下来,提供一些方法来从每个CRefCounted *获取引用对象集。它可以是一个虚拟集<CRefCounted *> CRefCounted :: get_children()或者任何适合东西。现在,我们可以走线了。

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