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

C++程序出现double free错误,求解决对象存入unordered_map的问题

C++程序double free错误分析及对象存入unordered_map的正确方式

错误信息

free(): double free detected in tcache 2 Aborted (core dumped)

原始代码

#include <fstream>
#include <string_view>
#include <unordered_map>
#include "nlohmann/json.hpp"
using json = nlohmann::json;

class Strategy {
private:
    int mActionNum;
    double mNormSum;
    double* mRegretSum;
    double* mStrategy;
    double* mStrategySum;
    double* mAverageStrategy;

public:
    Strategy (const int actionNum): mActionNum {actionNum} {
        mRegretSum = new double[actionNum];
        mStrategy = new double[actionNum];
        mStrategySum = new double[actionNum];
        mAverageStrategy = new double[actionNum];

        for (int a = 0; a < actionNum; ++a) {
            mRegretSum[a] = 0.0;
            mStrategy[a] = 1.0 / (double) actionNum;
            mStrategySum[a] = 0.0;
            mAverageStrategy[a] = 0.0;
        }
    }

    ~Strategy() {
        delete[] mRegretSum;
        delete[] mStrategy;
        delete[] mStrategySum;
        delete[] mAverageStrategy;
    }

    const double* get(const double weight) {
        mNormSum = 0.0;

        for (int a = 0; a < mActionNum; ++a) {
            mStrategy[a] = mRegretSum[a] > 0 ? mRegretSum[a] : 0;
            mNormSum += mStrategy[a];
        }

        for (int a = 0; a < mActionNum; ++a) {
            if (mNormSum > 0) {
                mStrategy[a] /= mNormSum;
            } else {
                mStrategy[a] = 1 / mNormSum;
            }

            mStrategySum[a] += weight * mStrategy[a];
        }

        return mStrategy;
    }

    const double* get_avg() {
        mNormSum = 0.0;

        for (int a = 0; a < mActionNum; ++a) {
            mNormSum += mStrategySum[a];
        }

        for (int a = 0; a < mActionNum; ++a) {
            if (mNormSum > 0) {
                mAverageStrategy[a] = mStrategySum[a] / mNormSum;
            } else {
                mAverageStrategy[a] = 1.0 / (double) mActionNum;
            }
        }

        return mAverageStrategy;
    }
};


int main () {
    std::ifstream f("/home/tomas/Dropbox/strategy.json");
    json data = json::parse(f);

    std::unordered_map<std::string_view, Strategy> strategies;
    std::string info_set {};

    for (auto& e : data) {
        info_set = e["cluster"].get<std::string>()
                    + ',' + e["history"].get<std::string>();

        Strategy strat { static_cast<int>(e["regret_sum"].size()) };
        strategies.insert({info_set, strat});
    }
    return 0;
}

你的猜测与尝试

你猜测循环中的Strategy实例会被销毁,但这不是核心问题。你尝试将对象创建在单独函数中返回,问题依旧存在。

问题根源

Strategy类没有正确处理拷贝操作。C++默认的拷贝构造函数和赋值运算符是浅拷贝——只会复制指针的值,而不是指针指向的内存块。当你把strat插入到unordered_map时,会创建一个拷贝,原对象和拷贝对象的指针指向同一块堆内存。当原对象(循环里的strat)析构时,会释放这些内存;之后map里的拷贝对象析构时,又会尝试释放同一块内存,就触发了double free错误。

解决方案

下面提供三种可行的解决方式,按推荐程度排序:

1. 用标准容器替代原始指针(最推荐)

把Strategy里的double*换成std::vector<double>,标准容器会自动处理内存的拷贝、释放,不需要手动写析构函数,默认的拷贝构造和赋值就能正常工作。

修改后的Strategy类:

#include <vector> // 需要包含头文件

class Strategy {
private:
    int mActionNum;
    double mNormSum;
    std::vector<double> mRegretSum;
    std::vector<double> mStrategy;
    std::vector<double> mStrategySum;
    std::vector<double> mAverageStrategy;

public:
    Strategy (const int actionNum): 
        mActionNum {actionNum},
        mRegretSum(actionNum, 0.0),
        mStrategy(actionNum, 1.0 / (double)actionNum),
        mStrategySum(actionNum, 0.0),
        mAverageStrategy(actionNum, 0.0)
    {}

    // 不需要手动写析构函数,vector会自动管理内存

    const double* get(const double weight) {
        mNormSum = 0.0;

        for (int a = 0; a < mActionNum; ++a) {
            mStrategy[a] = mRegretSum[a] > 0 ? mRegretSum[a] : 0;
            mNormSum += mStrategy[a];
        }

        for (int a = 0; a < mActionNum; ++a) {
            if (mNormSum > 0) {
                mStrategy[a] /= mNormSum;
            } else {
                mStrategy[a] = 1.0 / (double)mActionNum; // 修复原代码除以0的问题
            }

            mStrategySum[a] += weight * mStrategy[a];
        }

        return mStrategy.data(); // 返回vector底层数组的指针
    }

    const double* get_avg() {
        mNormSum = 0.0;

        for (int a = 0; a < mActionNum; ++a) {
            mNormSum += mStrategySum[a];
        }

        for (int a = 0; a < mActionNum; ++a) {
            if (mNormSum > 0) {
                mAverageStrategy[a] = mStrategySum[a] / mNormSum;
            } else {
                mAverageStrategy[a] = 1.0 / (double) mActionNum;
            }
        }

        return mAverageStrategy.data();
    }
};

这样修改后,直接用原来的main代码就能正常工作,插入到unordered_map时的拷贝操作会正确复制所有数据,不会出现内存重复释放的问题。

2. 禁用拷贝,使用移动语义

如果一定要用原始指针,可以禁用拷贝构造和赋值运算符,实现移动构造和移动赋值,让对象的所有权转移而不是拷贝。

修改Strategy类:

class Strategy {
    // ... 原有成员和构造函数不变 ...

    // 禁用拷贝构造和赋值
    Strategy(const Strategy&) = delete;
    Strategy& operator=(const Strategy&) = delete;

    // 实现移动构造
    Strategy(Strategy&& other) noexcept:
        mActionNum(other.mActionNum),
        mNormSum(other.mNormSum),
        mRegretSum(other.mRegretSum),
        mStrategy(other.mStrategy),
        mStrategySum(other.mStrategySum),
        mAverageStrategy(other.mAverageStrategy)
    {
        // 将原对象的指针置空,避免析构时释放内存
        other.mRegretSum = nullptr;
        other.mStrategy = nullptr;
        other.mStrategySum = nullptr;
        other.mAverageStrategy = nullptr;
    }

    // 实现移动赋值
    Strategy& operator=(Strategy&& other) noexcept {
        if (this != &other) {
            // 先释放当前对象的内存
            delete[] mRegretSum;
            delete[] mStrategy;
            delete[] mStrategySum;
            delete[] mAverageStrategy;

            // 转移所有权
            mActionNum = other.mActionNum;
            mNormSum = other.mNormSum;
            mRegretSum = other.mRegretSum;
            mStrategy = other.mStrategy;
            mStrategySum = other.mStrategySum;
            mAverageStrategy = other.mAverageStrategy;

            // 置空原对象指针
            other.mRegretSum = nullptr;
            other.mStrategy = nullptr;
            other.mStrategySum = nullptr;
            other.mAverageStrategy = nullptr;
        }
        return *this;
    }

    // ... 原有析构函数不变 ...
};

然后main里插入时用std::move转移所有权:

strategies.insert({info_set, std::move(strat)});
// 或者直接emplace构造,避免临时对象
strategies.emplace(info_set, static_cast<int>(e["regret_sum"].size()));

3. 使用智能指针存储对象

把unordered_map的value类型改成std::unique_ptr<Strategy>,用智能指针自动管理内存,避免拷贝问题:

修改main函数:

#include <memory> // 包含头文件

int main () {
    std::ifstream f("/home/tomas/Dropbox/strategy.json");
    json data = json::parse(f);

    std::unordered_map<std::string_view, std::unique_ptr<Strategy>> strategies;
    std::string info_set {};

    for (auto& e : data) {
        info_set = e["cluster"].get<std::string>()
                    + ',' + e["history"].get<std::string>();

        // 创建智能指针,直接插入到map中
        strategies.insert({info_set, std::make_unique<Strategy>(static_cast<int>(e["regret_sum"].size()))});
    }
    return 0;
}

这种方式不需要修改Strategy类(但要确保没有拷贝操作),智能指针会自动在对象不再被使用时释放内存。

关于对象存入unordered_map的说明

  • 如果对象支持拷贝(比如用vector的方案),可以直接用insert或emplace:
    • insert会创建对象的拷贝,或者用std::move转移所有权;
    • emplace直接在map的内存中构造对象,避免临时对象,效率更高。
  • 如果对象不支持拷贝(比如禁用拷贝的方案),必须用移动语义或者智能指针,确保对象所有权正确转移,避免无效的拷贝操作。
  • 关于std::string_view作为key:要注意info_set是局部变量,string_view指向的是它的内存,当info_set在循环中被修改时,map里的key会变成无效的悬垂指针。建议把map的key改成std::string,这样会存储字符串的拷贝,避免悬空引用问题。

内容的提问来源于stack exchange,提问作者ViciniVicini

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 01:35:21