代码之家  ›  专栏  ›  技术社区  ›  Thomas Koschel

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

  •  20
  • Thomas Koschel  · 技术社区  · 17 年前

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

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

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

    但是,有人对分组的“算法”有什么想法吗?

    发展发生在 专业版 .

    (我希望有人能理解我试图解释的……)

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

    12 回复  |  直到 17 年前
        1
  •  16
  •   paercebal    17 年前

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

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

        2
  •  6
  •   yrp    17 年前

    我这样做的方式很简单: -在每个add ef()记录调用堆栈上, -matching Release()将其删除。 这样,在程序结束时,我只剩下了add ef(),而没有处理Release。不需要配对,

        3
  •  4
  •   Drealmer    17 年前

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

    你也应该考虑这个很棒的工具: http://www.codeproject.com/KB/applications/visualleakdetector.aspx

        4
  •  4
  •   Constantin    17 年前

    要检测引用周期,您需要一个所有引用计数对象的图形。这样的图不容易构造,但可以做到。

    创建全球 set<CRefCounted*> 登记活体参考计数对象。如果你有通用的add ef()实现,这会更容易——只需添加 this 当对象的引用计数从0变为1时,指向集合的指针。同样,在Release()中,当引用计数从1变为0时,从集合中删除对象。

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

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

        5
  •  4
  •   Joe    14 年前

    我所做的是用一个类来包装智能指针,该类接受 功能 线 参数。每次调用构造函数时,递增该函数和行的计数,每次调用析构函数时,递减计数。然后,编写一个函数来转储函数/行/计数信息。这告诉你所有的参考资料都是在哪里创建的

        6
  •  2
  •   LuisRa    17 年前

    我为解决这个问题所做的就是推翻 malloc/new & 自由/删除 运算符,以便他们在数据结构中尽可能多地跟踪您正在执行的操作。

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

    写作时 自由/删除 在例程中,我还跟踪调用者的地址和指针信息。然后我回头看列表,试着匹配 malloc/new 对方用指针作为我的钥匙。如果我找不到,就举起红旗。

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

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

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

        7
  •  2
  •   yrp    17 年前

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

        8
  •  2
  •   M2tM    11 年前

    既然你说你使用的是Windows,你也许可以利用微软的用户模式转储堆实用程序, UMDH ,随附 Debugging Tools for Windows .UMDH会对应用程序的内存使用情况进行快照,记录每次分配所使用的堆栈,并允许您比较多个快照,以查看对分配器的哪些调用“泄漏”了内存。它还使用dbghelp.dll将堆栈跟踪转换为符号。

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

        9
  •  1
  •   Mike Stone    17 年前

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

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

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

        10
  •  1
  •   Doug T.    17 年前

    对于Windows,请查看:

    MFC Memory Leak Detection

        11
  •  1
  •   0124816    17 年前

    我非常喜欢 Google's Heapchecker --它不会捕获所有泄漏,但会捕获其中的大部分。 ( 提示 :将其链接到所有单元测试中。)

        12
  •  0
  •   mariosoft    13 年前

    第一步可能是知道哪个类正在泄漏。 一旦你知道了,你就可以找到谁在增加引用: 1.在shared_ptr包装的类的构造函数上放置断点。 2.当shared_ptr内的调试器增加引用计数时,请介入:查看变量pn->pi_>使用计数_ 通过计算表达式来获取该变量的地址(类似于:&the->pn->pi_.use_count_),您将得到一个地址 3.在visual studio调试器中,转到调试->;新断点->新建数据断点。.. 输入变量的地址 4.运行程序。每当代码中的某个点增加和减少引用计数器时,您的程序都会停止。 然后你需要检查它们是否匹配。