基于Map节点对象与指针的引用计数实现段错误问题排查
问题分析与修复
原代码实现
struct ptr{ const node *ptr; static size_t _c; ptr(const node *_ptr = nullptr) : ptr(_ptr) { if(ptr) _c++; } ptr(const ptr& rhs) { ptr = rhs.ptr; if(ptr) _c++;} ptr(const ptr&& rhs) { ptr = rhs.ptr; if(ptr) _c++;} inline operator node() const{ return *ptr; } inline const node* operator->() const { return ptr;} inline ptr& operator=(const ptr& rhs) { if(!ptr && rhs.ptr != nullptr ) _c++; ptr = rhs.ptr; return *this; } inline ptr& operator=(const ptr&& rhs) { if(!ptr && rhs.ptr != nullptr ) _c++; ptr = rhs.ptr; return *this; } inline bool operator<(const ptr& rhs) const { return ptr < rhs.ptr; } inline bool operator == (const ptr& rhs) const { return ptr == rhs.ptr; } ~ptr() { if(ptr != nullptr) _c--, ptr = 0 ; if(_c == _mp.size()) _mp.clear(); } }; static std::map<node, ptr> _mp; static size_t ptr::nc = 0; ptr ptrof(node &pn) { auto r = _mp.emplace( pn, ptr(nullptr) ); if (r.second) r.first->second = ptr(&(r.first->first)); return r.first->second; }
设计思路说明
以map尺寸粒度而非单个节点进行引用计数
使用_c管理存储节点的清理时机(调用clear())
该设计优于shared_ptr的引用计数方案,后者为每个分配的节点维护独立计数器
//(此处仅维护一个计数器)
核心问题与分析
你这段代码的目标是用单个计数器管理map所有节点的引用,节省shared_ptr的计数器开销,但调用_mp.clear()时触发段错误,主要原因和逻辑问题有这些:
- 递归调用死循环:调用
_mp.clear()会逐个销毁map内的ptr对象,每个ptr析构时又会检查_c == _mp.size(),此时_mp.size()正在动态减少,条件再次满足后会递归调用clear(),直接导致内存访问异常。 - 引用计数逻辑混乱:
- 移动构造/赋值不该递增计数器:移动语义是转移所有权,总引用数不变,原代码里移动操作也加计数,会导致计数虚高。
- 赋值运算符没处理原指针的计数递减:比如当前
ptr指向A节点,赋值成B节点时,A的引用计数没减少,会导致计数错误。 - 变量名混淆:声明了
static size_t _c,却初始化ptr::nc,属于低级错误,计数根本没正确初始化。
- 清理条件逻辑错误:
_c是所有ptr的总引用数,_mp.size()是map内节点数,map里每个节点的value本身就是一个ptr,所以内部引用数等于_mp.size(),外部引用数应该是_c - _mp.size()。当外部引用为0时(_c == _mp.size())才该清理,但清理时触发的内部ptr析构会让_c继续减少,导致条件反复触发。
修复后的代码实现
#include <map> // 补充node的必要实现(需支持operator<作为map的key) struct node { int val; bool operator<(const node& other) const { return val < other.val; } }; struct ptr { const node* ptr; static size_t ref_count; static bool is_clearing; // 标记是否正在清理map,避免递归 ptr(const node* _ptr = nullptr) : ptr(_ptr) { if (ptr) ref_count++; } // 拷贝构造:递增引用计数 ptr(const ptr& rhs) : ptr(rhs.ptr) { if (ptr) ref_count++; } // 移动构造:转移所有权,原对象指针置空,不改变总引用数 ptr(ptr&& rhs) noexcept : ptr(rhs.ptr) { rhs.ptr = nullptr; } inline operator node() const { return *ptr; } inline const node* operator->() const { return ptr; } // 拷贝赋值:先处理原指针的计数递减,再处理新指针的递增 ptr& operator=(const ptr& rhs) { if (this == &rhs) return *this; // 避免自赋值 if (ptr) ref_count--; ptr = rhs.ptr; if (ptr) ref_count++; return *this; } // 移动赋值:转移所有权,处理原指针,原对象指针置空 ptr& operator=(ptr&& rhs) noexcept { if (this == &rhs) return *this; if (ptr) ref_count--; ptr = rhs.ptr; rhs.ptr = nullptr; return *this; } inline bool operator<(const ptr& rhs) const { return ptr < rhs.ptr; } inline bool operator==(const ptr& rhs) const { return ptr == rhs.ptr; } ~ptr() { if (!ptr) return; ref_count--; ptr = nullptr; // 只有外部引用为0,且不在清理状态时才触发map清理 if (ref_count == _mp.size() && !is_clearing) { is_clearing = true; _mp.clear(); is_clearing = false; } } }; // 初始化静态成员 size_t ptr::ref_count = 0; bool ptr::is_clearing = false; static std::map<node, ptr> _mp; ptr ptrof(node& pn) { auto r = _mp.emplace(pn, ptr(nullptr)); if (r.second) { // 给map内的value赋值指向当前key的ptr,属于内部引用 r.first->second = ptr(&(r.first->first)); } return r.first->second; }
修复要点说明
- 新增
is_clearing标志位,避免_mp.clear()触发递归调用,彻底解决段错误问题。 - 修正移动构造/赋值逻辑,符合C++移动语义的预期,不再额外增加引用计数。
- 修复赋值运算符的计数逻辑,处理原指针的计数递减,避免计数错误。
- 统一引用计数变量名,修复原代码中
_c和nc的命名混淆问题。 - 明确清理条件:只有当外部引用数为0(总引用数等于map内部引用数)时,才触发map清理。
内容的提问来源于stack exchange,提问作者U J
相关产品推荐
相关产品推荐

