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

声明nlohmann/json对象后控制台输出异常,原因何在?

问题分析:JSON对象引发的野指针访问异常

代码定义

Test类代码:

#include <iostream>
#include <fstream>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using namespace std;

class Test{

    public:
        Test *arrPtr[2];
        double data = 0.0;
        char label = ' ';
        char _op = ' ';

    Test(){
        this->arrPtr[0] = 0;
        this->arrPtr[1] = 0;
    };

    Test(double in, char l){
        this->data = in;
        this->label = l;
        this->arrPtr[0] = 0;
        this->arrPtr[1] = 0;
    };    

    Test(double in, Test* obj, char op){
        this->data = in;
        this->_op = op;
        this->arrPtr[0] = &(obj[0]);
        this->arrPtr[1] = &(obj[1]);
    };

    Test operator + (Test other){
        Test arr[] = {*this, other}; // arr contains calling object(this) and passed in other
        Test out(data + other.data, arr, '+'); // arr gets passed as pointer

    return out;
    }
};

main函数代码:

int main(){

    Test a(100.0, 'a'); 
    Test b(100.0, 'b');
    Test c; c.label = 'c'; 

    c = a + b;

    Test arr[] = { a, b, c };

    for (Test obj : arr){

        if(*(obj.arrPtr))
            cout << obj.arrPtr[0]->label;

        json j;
    };
};

问题现象

移除json j;行时终端输出正确字符;保留该行时,终端输出错误字符。

根本原因

这和编码完全无关,核心是野指针访问,JSON对象只是恰好触发了问题:

  1. 在operator+函数中,Test arr[] = {*this, other};是栈上的局部数组,函数执行完毕返回后,这个数组会被销毁,对应的栈内存会被标记为可复用。
  2. 创建的out对象的arrPtr指向了这个已销毁的局部数组的元素,此时arrPtr就成了野指针。
  3. 当没有json j;时,栈上原来的内存还没被新数据覆盖,访问野指针时刚好能拿到残留的正确值;但创建json j时,这个对象会占用栈空间,覆盖了原局部数组的内存区域,导致野指针访问到错误的垃圾数据。

修复方案

要避免让指针指向栈上的临时对象,改用堆分配存储需要持久化的对象,同时遵循C++的三法则(Rule of Three)管理内存:

  1. 修改operator+,用堆分配数组替代局部栈数组:
Test operator + (Test other){
    // 堆分配数组,确保对象在函数返回后仍存在
    Test* arr = new Test[2];
    arr[0] = *this;
    arr[1] = other;
    Test out(data + other.data, arr, '+');
    return out;
}
  1. 添加析构函数释放堆内存,避免泄漏:
~Test(){
    // 检查指针是否有效,释放堆数组
    if(arrPtr[0] != nullptr){
        delete[] arrPtr[0];
        // 释放后清空指针,避免悬空
        arrPtr[0] = nullptr;
        arrPtr[1] = nullptr;
    }
}
  1. 实现拷贝构造函数和赋值运算符,避免浅拷贝导致重复释放内存:
// 拷贝构造函数
Test(const Test& other){
    data = other.data;
    label = other.label;
    _op = other._op;
    // 对于堆上的数组,需要深拷贝
    if(other.arrPtr[0] != nullptr){
        arrPtr[0] = new Test[2];
        arrPtr[0][0] = other.arrPtr[0][0];
        arrPtr[0][1] = other.arrPtr[0][1];
        arrPtr[1] = &arrPtr[0][1];
    } else {
        arrPtr[0] = nullptr;
        arrPtr[1] = nullptr;
    }
}

// 赋值运算符
Test& operator=(const Test& other){
    if(this != &other){
        // 先释放当前对象的堆内存
        if(arrPtr[0] != nullptr){
            delete[] arrPtr[0];
        }
        // 拷贝基本成员
        data = other.data;
        label = other.label;
        _op = other._op;
        // 深拷贝堆数组
        if(other.arrPtr[0] != nullptr){
            arrPtr[0] = new Test[2];
            arrPtr[0][0] = other.arrPtr[0][0];
            arrPtr[0][1] = other.arrPtr[0][1];
            arrPtr[1] = &arrPtr[0][1];
        } else {
            arrPtr[0] = nullptr;
            arrPtr[1] = nullptr;
        }
    }
    return *this;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 11:50:34