c++ - 查找 shared_ptr 的引用计数增加的位置

标签 c++ memory-leaks valgrind shared-ptr

我有一些代码存在内存泄漏,因为它在其 shared_ptr 实例之间获取循环引用(这是两个 shared_ptr 实例指向的对象,每个对象都有一个内部shared_ptr 对另一个类实例的引用。这意味着两个类都不会被销毁,因为每个类实例仍在被另一个类实例使用,从而导致内存泄漏。在某些情况下,它是单个 shared_ptr 也引用自身的类的实例。)

通过 Valgrind 运行代码很有帮助,因为它告诉我内存最初分配的位置,但这不是循环引用的来源。我需要找到特定共享指针(Valgrind 提示的那个)的引用计数增加的所有位置,因为其中一个必须更改为 weak_ptr 才能解决问题。

我如何选择一个特定的 shared_ptr 并获取其引用计数增加的所有源代码行的列表?

我在 Linux 下运行 GCC/GDB 和 Valgrind,但欢迎使用平台中立的解决方案。

下面是一些演示问题的示例代码:

#include <boost/shared_ptr.hpp>

struct Base {
    int i;
};
struct A: public Base {
    int a;
    boost::shared_ptr<Base> ptrInA;
};
struct B: public Base {
    int b;
    boost::shared_ptr<Base> ptrInB;
};

int main(void)
{
    boost::shared_ptr<A> a(new A);   // Line 17
    boost::shared_ptr<B> b(new B);
    a->ptrInA = b;                   // Line 19
    b->ptrInB = a;
    return 0;
}

在 Valgrind 下运行时,它说:

HEAP SUMMARY:
    in use at exit: 96 bytes in 4 blocks
  total heap usage: 4 allocs, 0 frees, 96 bytes allocated

96 (24 direct, 72 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 4
   at 0x4C2A4F0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
   by 0x40099A: main (test.cpp:17)

LEAK SUMMARY:
   definitely lost: 24 bytes in 1 blocks
   indirectly lost: 72 bytes in 3 blocks

我正在寻找一种解决方案,它会指出源文件中的第 19-20 行是循环的可能原因,这样我就可以检查代码并决定是否需要更改它。

最佳答案

基于@dandan78 的方法。这是一个更详细的例子 GDB CLI,它在 shared_ptr 的引用计数更改上创建断点。

主要.cpp:

#include <iostream>
#include <memory>

using namespace std;

#define DBG(msg) std::cout << msg << std::endl;

class A {
    public:
        A(int i) {
            mI = i;
            DBG("A() this:"<<this<<" i:"<<mI);
        }
        ~A() {
            DBG("~A() this:"<<this<<" i:"<<mI);
        }
    private:
        int mI = 0;
};

int main() {
    std::shared_ptr<A> p1(new A(0x12345678));
    DBG("p1 use_count:"<<p1.use_count());
    {
        auto p2 = p1;
        DBG("p1 use_count:"<<p1.use_count());
        DBG("p2 use_count:"<<p2.use_count());
        auto p3 = p1;
        DBG("p1 use_count:"<<p1.use_count());
        DBG("p2 use_count:"<<p2.use_count());
        DBG("p3 use_count:"<<p3.use_count());
    }
    DBG("p1 use_count:"<<p1.use_count());
    return 0;
}

生成文件:

CXXFLAGS = -O0 -ggdb

main: main.cpp
    $(CXX) $(CXXFLAGS) -o $@ $<

程序的输出:

A() this:0x6c6fb0 i:305419896
p1 use_count:1
p1 use_count:2
p2 use_count:2
p1 use_count:3
p2 use_count:3
p3 use_count:3
p1 use_count:1
~A() this:0x6c6fb0 i:305419896

编译并运行gdb(不要把#注释粘贴到gdb):

make
gdb main 2>&1 | tee out.log

GDB session :

(gdb) b main.cpp:23   # right after the p1 initialization
(gdb) r
Thread 1 hit Breakpoint 1, main () at main.cpp:23
(gdb) x/2xg &p1
0x62fe00:       0x0000000000fd4a10      0x0000000000fd4a50
# First pointer points to the target A object, sencond points to the reference counter
# Inspect the refcount data:
(gdb) x/4xw 0x0000000000fd4a50
0xfd4a50:       0x00405670      0x00000000      0x00000003      0x00000001
# The third integer is use_count of the shared_ptr, which can be printed by:
(gdb) x/1xw 0x0000000000fd4a50 + 8
0xfd4a58:       0x00000001

# Add a watchpoint for the use_count address
(gdb) watch *(int*)(0x0000000000fd4a50 + 8)
Hardware watchpoint 2: *(int*)(0x0000000000fd4a50 + 8)
# Add commands for the new watchpoint 2:
(gdb) commands 2
bt             # backtrace
c              # continue
end            # end of the handler script

(gdb) c        # Continue the program

现在您可以检查 out.log 文件并分析 use_count 发生变化的所有回溯。

也可以直接添加 gdb 观察点:

watch *(*((int**)(&p1) + 1) + 2)
                   ^--------------- the shared_ptr variable
                         ^--------- +1 pointer to the right (+8 bytes in 64bit programm)
                              ^---- +2 integers to the right (+8 bytes)

如果您使用优化进行编译,shared_ptr 变量可能已经被优化掉了。只需在您的代码中直接打印它,然后获取 shared_ptr 对象的地址并将其粘贴到您的 gdb session 中:

std::cout << "p1:" << (void*)&p1 << std::endl;

关于c++ - 查找 shared_ptr 的引用计数增加的位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28291515/

相关文章:

c++ - 为什么我的小部件不显示?

c++ - 奇怪的 C++ 内存分配

c++ - 将 int 包装在一个类中会有任何性能损失吗?

c++ - void* 强制转换为 char* 而不是左值?

c - 我是否有可能知道我是否已在 C 中正确清理?

javascript - 为什么我的 Chrome 分析器没有显示我的对象的正确保留路径,以及为什么我的对象从未被释放?

c++ - windbg中 "Internal"输出中的 "!heap -h"是什么意思?

c - 无效的读取内存 - valgrind

Valgrind Massif工具输出图形界面?

c - 重新分配二维数组 - valgrind 错误