如何定位C++中使shared_ptr引用计数增加的代码行?
嘿,这个需求太常见了——排查引用计数异常、定位内存泄漏源头的时候,这招绝对管用。我给你分享几个实操性强的方法,帮你精准揪出哪一行代码让shared_ptr的引用计数从12涨到13:
方法一:自定义带跟踪的shared_ptr包装类
自己封装一层std::shared_ptr,在所有会触发引用计数增加的操作(拷贝构造、拷贝赋值,以及从weak_ptr::lock()转换过来的场景)里,打印调用栈和当前引用计数。这样不用依赖调试器,直接运行程序就能看到触发点:
#include <memory> #include <iostream> #include <execinfo.h> template<typename T> class TrackedSharedPtr { private: std::shared_ptr<T> ptr; void print_call_stack() { void* callstack[128]; int frames = backtrace(callstack, 128); char** strs = backtrace_symbols(callstack, frames); std::cout << "\n=== Reference count increased! ===" << '\n'; std::cout << "Call stack:\n"; // 跳过当前函数本身,只打印上层调用 for (int i = 1; i < frames; ++i) { std::cout << " " << strs[i] << '\n'; } free(strs); } public: // 基础构造函数 TrackedSharedPtr(T* p = nullptr) : ptr(p) {} // 拷贝构造(触发引用计数+1) TrackedSharedPtr(const TrackedSharedPtr& other) : ptr(other.ptr) { print_call_stack(); std::cout << "New ref count: " << ptr.use_count() << '\n'; } // 拷贝赋值(触发引用计数+1) TrackedSharedPtr& operator=(const TrackedSharedPtr& other) { if (this != &other) { ptr = other.ptr; print_call_stack(); std::cout << "New ref count: " << ptr.use_count() << '\n'; } return *this; } // 从std::shared_ptr转换(触发引用计数+1) TrackedSharedPtr(const std::shared_ptr<T>& other) : ptr(other) { print_call_stack(); std::cout << "New ref count: " << ptr.use_count() << '\n'; } // 暴露必要的原生接口 T* get() const { return ptr.get(); } T& operator*() const { return *ptr; } T* operator->() const { return ptr.operator->(); } long use_count() const { return ptr.use_count(); } };
使用时,把代码里的std::shared_ptr替换成TrackedSharedPtr即可。注意编译GCC/Clang时要加-rdynamic选项,这样才能正确解析调用栈的符号。
方法二:用调试器设置条件断点(无需改代码)
如果不想修改业务代码,直接用调试器就能搞定,以GDB为例:
- 启动调试会话后,先找到你要跟踪的shared_ptr实例,打印它的控制块信息。比如假设变量名叫
my_sp,可以用:p my_sp._M_ptr // 查看原生指针(GCC下) p my_sp.__cntrl_->__use_count // 查看当前引用计数 - 在
std::shared_ptr的拷贝构造函数上设置条件断点,只在引用计数即将从12变13时触发:break std::shared_ptr<YourType>::shared_ptr(const std::shared_ptr<YourType>&) condition <断点编号> my_sp.__cntrl_->__use_count == 12 - 继续运行程序,当断点触发时,输入
bt就能看到完整调用栈,直接定位到对应的代码行。
LLDB的操作逻辑类似,用breakpoint set命令配合条件即可。
方法三:Windows平台用Visual Studio调试工具
如果你在Visual Studio环境下开发,操作更直观:
- 启动调试,找到要跟踪的shared_ptr变量,右键添加到“监视”窗口。
- 展开变量的内部结构,找到
use_count()或者_Ref_count字段。 - 右键这个字段,选择“设置数据断点”,当引用计数发生变化时调试器会自动暂停。此时查看调用栈,就能找到触发引用计数增加的代码行。如果只想捕捉从12到13的变化,可以在断点条件里添加
old_value == 12。
额外提示
要注意那些隐式的拷贝操作:比如把shared_ptr按值传递给函数、作为函数返回值返回、或者存入容器(比如std::vector<std::shared_ptr<T>>)时,都会触发引用计数增加,这些场景上面的方法都能精准捕捉到。
内容的提问来源于stack exchange,提问作者Milad Sikaroudi
相关产品推荐
相关产品推荐

