我有一些代码有内存泄漏,因为它在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行作为循环的可能原因,因此我可以检查代码并决定是否需要更改它。
答案 0 :(得分:2)
您遇到设计错误。因此,您需要使用设计调试工具。
拿一支笔和一张纸。
为每个类类型绘制一个矩形。从包含shared_ptr的每个类中绘制一个箭头到它所持有的类
如果你找到一个圆圈,那就是你的问题。
现在,每个箭头或链接都是通过shared_ptr赋值在某处创建的 看看可疑箭头,那些关闭圆圈的箭头,看看它们是否被正确释放。
答案 1 :(得分:1)
虽然Yochai Timmer的方法适用于较小的项目,但我最近不得不在相当大的代码库上工作,使用shared_ptr
,Boost变体,到处都是。这是在Windows上,UI是在MFC之上完成的,主CWinApp
派生的应用程序类由shared_ptr
指向。并且它永远不会被破坏,导致一大堆析构函数没有被调用,结果出现了一些令人讨厌的行为。
在尝试了各种检漏仪之后,我通过让调试器在访问有问题的shared_ptr
的第一行中断,然后搜索相关的标题直到找到参考计数器的确切位置来解决问题。然后我在参考计数器的地址中添加了一个内存断点,并且每次递增/递减都会使VS调试器中断,直到ref ctr的值无法回退到其正常状态&#39;值。
就我而言,我知道shared_ptr
不应该有参考计数&gt; 2,应用程序的初始化完成后。当它达到3并且再也没有回到2时,我知道我发现了泄漏。而内存断点只需要大约1000次左右......
是的,我确定有更好的方法来追踪涉及shared_ptr
的内存泄漏,但如果所有其他方法都失败了,那么总是采用暴力方法来观察参考计数器。当然,详细信息取决于您的shared_ptr
实施以及您的申请的组织方式。
答案 2 :(得分:1)
基于@dandan78 的方法。这是一个更详细的 GDB 示例。
main.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 会话:
(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 会话中:
std::cout << "p1:" << (void*)&p1 << std::endl;