【问题标题】:How do I know who holds the shared_ptr<>?我怎么知道谁拥有 shared_ptr<>?
【发布时间】:2010-11-06 21:25:41
【问题描述】:

我在 C++ 应用程序中使用 boost::shared_ptr。内存问题真的很严重,应用程序占用大量内存。

但是,因为我把每一个新的对象都放到了一个 shared_ptr 中,所以当应用程序退出时,不会检测到内存泄漏。

必须有类似std::vector&lt;shared_ptr&lt;&gt; &gt; pool 之类的东西持有资源。调试时如何知道谁持有shared_ptr?

很难逐行查看代码。代码太多...

非常感谢!

【问题讨论】:

    标签: c++ boost memory-leaks shared-ptr


    【解决方案1】:

    您显然在应用程序中保留对对象的引用。这意味着您是故意将事情记在内存中的。这意味着,您没有内存泄漏。内存泄漏是指分配了内存,然后您没有保留对其地址的引用。

    基本上,您需要查看您的设计并弄清楚为什么要在内存中保留如此多的对象和数据,以及如何将其最小化。

    您有伪内存泄漏的一种可能性是您创建的对象比您想象的要多。尝试在所有包含“新”的语句上放置断点。查看您的应用程序构建的对象是否比您想象的要多,然后通读该代码。

    问题实际上不是内存泄漏,而是应用程序设计的问题。

    【讨论】:

    • 非常感谢!大约有20万行。所以很难检查每一个新的......是否有任何编译器宏来激活boost的参考检查能力(如果存在这种能力)。内存是由关于池的编程逻辑错误引起的,我敢肯定,但我找不到它。
    • 您仍然可以使用 shared_ptrs 发生内存泄漏。创建一个循环引用,它永远不会被删除,即使应用程序的其余部分不再引用它。瞬间内存泄漏!
    • 对未正确保留的对象的引用仍然是资源泄漏。这就是为什么 GC 程序仍然有泄漏的原因,通常是由于观察者模式 - 观察者在列表中,而不是可观察者,并且永远不会被删除。最终,每个add 都需要一个remove,就像每个new 都需要一个delete。完全相同的编程错误,导致完全相同的问题。 “资源”实际上只是一对函数,它们必须被调用相同次数并带有相应的参数,而“资源泄漏”就是当你没有这样做时发生的事情。
    【解决方案2】:

    仅通过查看shared_ptr,您无法知道“兄弟指针”在哪里。您可以测试一个是否为unique() 或在other methods 中获取use_count()

    【讨论】:

      【解决方案3】:

      您可能会通过循环遇到共享指针内存泄漏。发生的情况是您的共享对象可能持有对其他共享对象的引用,这些引用最终会回到原始对象。发生这种情况时,即使没有其他人可以访问对象,循环也会将所有引用计数保持为 1。解决方案是weak pointers

      【讨论】:

      • 非常感谢!我真的使用weak_ptr 作为资源观察者。所以我知道大量 shared_ptr 存在于内存中。我确定没有循环,某些模块设计不当,我正在尝试找出它。
      • "解决方案是弱指针。" 不,不是。解决方案是审查设计。
      • @curiousguy:是的,是的,要迂腐地解决循环所有权的真正解决方案是避免它。然而,有一些合法的问题可以用循环指针更好地解决。在这些情况下:由真正需要的循环引用引起的内存泄漏的解决方案是使用std::weak_ptr
      • @deft_code 你有一个用shared_ptr 实现的强引用循环。如果将一个shared_ptr 替换为weak_ptr,则不再有强引用循环; IOW:您不再拥有拥有智能指针的循环。您通过抑制循环所有权“解决了”循环所有权问题。如果“解决方案”是可以接受的,则意味着您实际上从来不需要循环所有权的语义weak_ptr 不是魔法。
      【解决方案4】:

      尝试重构您的一些代码,以便通过在某些地方使用弱指针而不是共享指针来更明确地表达所有权。

      在查看您的类层次结构时,可以确定哪个类真正应该持有共享指针,而哪个只需要弱指针,因此您可以避免循环(如果有的话)并且如果“真正的”所有者对象被破坏, “非所有者”对象应该已经消失了。如果发现某些对象过早丢失指针,则必须查看应用程序中的对象销毁顺序并进行修复。

      【讨论】:

      • 你说的是弱引用可以用来调试:如果弱引用死了,我们需要使用它,说明程序有bug。这是我第一次看到这个想法在操作系统上清楚地表达了——weak_ptr 的其他一些海报使用似乎暗示它是一个调试工具,但他们并没有说清楚。备注:这比使用常规 C++ 指针效率低:也许我们需要一个可以定义为 weak_ptr 或“常规指针”智能指针的 checked_ptr
      【解决方案5】:

      无法从程序中分辨出哪些对象拥有 shared_ptr。如果您在 Linux 上,调试内存泄漏的一种可靠方法是Valgrind 工具——虽然它不会直接回答您的问题,但它会告诉您分配内存的位置,这通常足以解决问题。我想 Windows 有类似的工具,但我不知道哪个最好。

      【讨论】:

        【解决方案6】:

        shared_ptr 的普遍使用几乎不可避免地会导致不必要的和看不见的内存占用。

        循环引用是一个众所周知的原因,其中一些可能是间接的并且难以发现,尤其是在由多个程序员编写的复杂代码中;程序员可能决定一个对象需要引用另一个对象作为快速修复,并且没有时间检查所有代码以查看他是否正在关闭一个循环。这种危害被大大低估了。

        不太为人所知的是未发布引用的问题。如果一个对象被共享给许多 shared_ptrs,那么它不会被销毁,直到它们中的每一个都归零或超出范围。很容易忽略其中一个引用,并最终得到潜伏在内存中看不见的对象,而您认为您已经完成了。

        虽然严格来说这些不是内存泄漏(它会在程序退出之前全部释放),但它们同样有害且更难检测。

        这些问题是权宜之计虚假声明的后果: 1. 将您真正想要的单一所有权声明为 shared_ptr。 scoped_ptr 是正确的,但是对该对象的任何其他引用都必须是原始指针,它可能会悬空。 2. 将你真正想要的被动观察引用声明为 shared_ptr。 weak_ptr 是正确的,但是每次你想使用它时,你都会很麻烦地将它转换为 share_ptr。

        我怀疑您的项目是这种做法可能给您带来的麻烦的一个很好的例子。

        如果您有一个内存密集型应用程序,您确实需要单一所有权,以便您的设计可以显式控制对象生命周期。

        单一所有权 opObject=NULL;肯定会删除该对象,它会立即执行。

        共享所有权 spObject=NULL; ........谁知道?......

        【讨论】:

        • "程序员可能决定一个对象需要引用另一个对象作为快速修复,并且没有时间检查所有代码以查看他是否正在关闭循环。 i>” 他不必“阅读代码”。他应该阅读设计文件
        • 现实世界中很多项目都没有设计文档的概念:)
        • @curiousguy 我更喜欢代码来设计文档,因为文档可以说谎,并且代码可以编译。
        • @M2tM 评论可能会撒谎。变量名可能会撒谎(尤其是如果您尝试将类型注释放在变量名中)。在设计更改后,函数名称经常会撒谎,或者至少具有误导性或无用。代码可以包含过去版本的痕迹,这些痕迹就像蛇的腿一样有用。进化很多的代码通常会产生误导。
        • 代码肯定会产生误导!制作一个编写良好的软件并不是一件容易的事,更难(或者我应该说,需要持续努力)在保持质量的同时更改现有系统。但是 cmets 和文档不会编译,并且通常是与他们试图描述的事物的功能不同步的第一件事。当然,变量名和函数名的命名可能很糟糕,但在我职业生涯中遇到的代码库中似乎不太常见。通常,设计文档是在代码之前编写的,并且从不更新。
        【解决方案7】:

        如果您在 Windows 上,我将建议使用 UMDH。这是一个非常强大的工具。使用它查找您希望被释放的每个事务/时间段的分配,然后找到持有它们的人。

        有关此 SO 答案的更多信息 Find memory leaks caused by smart pointers

        【讨论】:

          【解决方案8】:

          我们已经完成的悬空或循环智能指针引用的一种解决方案是自定义智能指针类以添加仅用于调试的簿记功能。每当智能指针添加对对象的引用时,它都会获取堆栈跟踪并将其放入映射中,每个条目都跟踪该映射

          1. 被分配对象的地址(指针指向的地方)
          2. 持有对该对象的引用的每个智能指针对象的地址
          3. 每个智能指针的构建时间对应的堆栈跟踪

          当智能指针超出范围时,它在映射中的条目将被删除。当一个对象的最后一个智能指针被销毁时,被指针对象在映射中的条目被删除。

          然后我们有一个“跟踪泄漏”命令,它有两个功能:“[重新]开始泄漏跟踪”(清除整个地图并启用跟踪,如果还没有)和“打印打开的引用”,它显示所有未完成的自发出“开始泄漏跟踪”命令以来创建的智能指针引用。由于您可以看到这些智能指针出现的位置的堆栈跟踪,因此您可以轻松地准确知道谁在阻止您的对象被释放。它在开启时会减慢速度,因此我们不会一直打开它。

          这是一个相当大的工作量来实现,但如果你有一个经常发生这种情况的代码库,那绝对值得。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-04-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-10-16
            相关资源
            最近更新 更多