You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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()时触发段错误,主要原因和逻辑问题有这些:

  1. 递归调用死循环:调用_mp.clear()会逐个销毁map内的ptr对象,每个ptr析构时又会检查_c == _mp.size(),此时_mp.size()正在动态减少,条件再次满足后会递归调用clear(),直接导致内存访问异常。
  2. 引用计数逻辑混乱:
    • 移动构造/赋值不该递增计数器:移动语义是转移所有权,总引用数不变,原代码里移动操作也加计数,会导致计数虚高。
    • 赋值运算符没处理原指针的计数递减:比如当前ptr指向A节点,赋值成B节点时,A的引用计数没减少,会导致计数错误。
    • 变量名混淆:声明了static size_t _c,却初始化ptr::nc,属于低级错误,计数根本没正确初始化。
  3. 清理条件逻辑错误:_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 21:50:57