声明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对象只是恰好触发了问题:
- 在
operator+函数中,Test arr[] = {*this, other};是栈上的局部数组,函数执行完毕返回后,这个数组会被销毁,对应的栈内存会被标记为可复用。 - 创建的
out对象的arrPtr指向了这个已销毁的局部数组的元素,此时arrPtr就成了野指针。 - 当没有
json j;时,栈上原来的内存还没被新数据覆盖,访问野指针时刚好能拿到残留的正确值;但创建json j时,这个对象会占用栈空间,覆盖了原局部数组的内存区域,导致野指针访问到错误的垃圾数据。
修复方案
要避免让指针指向栈上的临时对象,改用堆分配存储需要持久化的对象,同时遵循C++的三法则(Rule of Three)管理内存:
- 修改
operator+,用堆分配数组替代局部栈数组:
Test operator + (Test other){ // 堆分配数组,确保对象在函数返回后仍存在 Test* arr = new Test[2]; arr[0] = *this; arr[1] = other; Test out(data + other.data, arr, '+'); return out; }
- 添加析构函数释放堆内存,避免泄漏:
~Test(){ // 检查指针是否有效,释放堆数组 if(arrPtr[0] != nullptr){ delete[] arrPtr[0]; // 释放后清空指针,避免悬空 arrPtr[0] = nullptr; arrPtr[1] = nullptr; } }
- 实现拷贝构造函数和赋值运算符,避免浅拷贝导致重复释放内存:
// 拷贝构造函数 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
相关产品推荐
相关产品推荐

