修改一行代码,解决在C++ Builder平台上JSONCPP向数组添加元素的Bug

最近在C++ Builder里使用jsoncpp 0.10.2(0.y.z分支)的过程中碰到一个bug,创建的数组,无法超过5个元素,测试代码如下:


int j = 0;
int count = 20;
Json::Value root;
Json::Value item;

for (int i = 0; i < count; i++)
{
root[i] = i;
j = root.size();
}


在我的实际项目中,如果数组只有1个是元素(该元素稍微有点大的JSON对象),也有可能出现这个元素的值错误的故障,超过5个肯定出错。


在github上创建了一个issue,作者响应很快,但由于开发环境不同,他测试各个分支的代码结果都正常,不能重现故障。我在几天里,陆续调试过几次后,终于逐渐发现了故障原因:


在jsoncpp的源码里进行Debug,发现实际创建的数组成员数量并没有少,但 value_.map_ 的键CZString的成员index_是 0,1,2,3,4,0,1,2,3,4, 0,1,2,3,4,0,1,2,3,4,按正确代码应该是从0一直递增到19。而数组的size()函数是按最后一对键值的index_加1来计算大小的:

case arrayValue: // size of the array is highest index + 1
if (!value_.map_->empty()) 

{
ObjectValues::const_iterator itLast = value_.map_->end();
--itLast;


return (*itLast).first.index() + 1;

 }
return 0;


这里我不能理解作者不直接调用map的size()函数,虽然按最后一个元素的index_的值加1来计算数组大小在逻辑上无错误。不过后面经过思考,数组可能有为null的元素不在map里面,但map最后一个元素肯定不为null,作者的思路并没有错。发现这个环节的故障后,再经过几次调试和思考,发现了以下这个拷贝构造函数有问题:


Value::CZString::CZString(const CZString& other)
    : cstr_(other.storage_.policy_ != noDuplication && other.cstr_ != 0  ? duplicateStringValue(other.cstr_, other.storage_.length_) : other.cstr_)
{
  storage_.policy_ = (other.cstr_   ? (other.storage_.policy_ == noDuplication  ? noDuplication : duplicate)  : other.storage_.policy_);
  storage_.length_ = other.storage_.length_;
}


刚开始看这个拷贝构造函数,以为没有处理index_这个成员变量。将此构造函数修改为以下版本,解决了故障:


Value::CZString::CZString(const CZString& other)
: cstr_(((other.storage_.policy_ != noDuplication) && (other.cstr_ != 0)) ? duplicateStringValue(other.cstr_, other.storage_.length_)
                : other.cstr_)
{
if (other.cstr_ != 0)
{
storage_.policy_ = (other.storage_.policy_ == noDuplication) ? noDuplication : duplicate;
storage_.length_ = other.storage_.length_;
}
else
{
this->index_ = other.index_; 
}
}


再次更新issue,作者确认是bug,同时对该bug未影响其他用户感到奇怪。我再次检查了这个函数,考虑到index_ 和 storage_ 在同一个union里,感觉按以前的拷贝构造函数的写法index_也应该被复制。继续研究,接着发现了一个的问题:


在子类CZString的定义中:
enum DuplicationPolicy

 {
noDuplication = 0,
duplicate,
duplicateOnCopy
};

这个enum的值是从0到2的,接着定义了:

struct StringStorage 

{
DuplicationPolicy policy_: 2;
unsigned length_: 30; // 1GB max

};


union

{

ArrayIndex index_;

StringStorage storage_;

};


很显然,作者希望StringStorage 刚好只使用32bit,刚好4个字节,但我在CB里用sizeof测试StringStorage其实是8个字节,因为2bit整数有1个是符号位,只能支持0、1、-1,DuplicationPolicy 的值从0到3,作为整数至少需要3bit,所以CB偷偷将其扩大了,超过32bi对齐后t就是8字节。将这个结构的定义改为:

struct StringStorage

 {
unsigned policy_: 2;
unsigned length_: 30; // 1GB max

};

这样改了后,大小就变成4字节了。此时将Value::CZString::CZString(const CZString& other)恢复以前的版本,bug消失了!

当StringStorage 按以前定义是8字节时(可能在大多数编译器上是4字节),复制函数:

void Value::CZString::swap(CZString& other) 
{
std::swap(cstr_, other.cstr_);
std::swap(index_, other.index_);
}

是有问题的,这时std::swap(index_, other.index_)只交换了4字节的 index_ 4,但作为8字节的storage_只被交换了一个成员。估计这就是bug的原因。

jsoncpp的开发者的最新反馈:https://github.com/open-source-parsers/jsoncpp/pull/266

此bug的修复代码已合并到jsoncpp的各个分支里:https://github.com/open-source-parsers/jsoncpp/commit/28d086e1d991e86a8ea0b3a40a52ba908f77992b



### 回答1: JSONCPP是一个用于解析和生成JSON数据的C++库。要读入数组,首先需要导入jsoncpp库,并包含相关的头文件: ```cpp #include <iostream> #include <fstream> #include <json/json.h> ``` 然后,可以使用Json::Value对象来解析JSON数据。假设我们有一个JSON文件,存储了一个包含多个元素的数组,文件名为data.json: ```json { "array": [1, 2, 3, 4, 5] } ``` 我们可以按照以下步骤读入数组: ```cpp int main() { // 读取JSON文件 std::ifstream file("data.json"); std::string str; // 将文件内容读入字符串 if (file) { file.seekg(0, std::ios::end); str.reserve(file.tellg()); file.seekg(0, std::ios::beg); str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); file.close(); } // 解析JSON数据 Json::Value root; Json::CharReaderBuilder builder; Json::CharReader* reader = builder.newCharReader(); std::string errors; // 解析字符串为JSON对象 bool parsingSuccessful = reader->parse(str.c_str(), str.c_str() + str.size(), &root, &errors); if (!parsingSuccessful) { std::cout << "Failed to parse JSON: " << errors; return 1; } // 读取数组 Json::Value array = root["array"]; if (array.isArray()) { for (int i = 0; i < array.size(); ++i) { std::cout << array[i].asInt() << " "; } } return 0; } ``` 以上代码读取JSON文件data.json,将其内容解析为JSON对象,并获取其中名为"array"的数组。然后,我们遍历数组中的每个元素,并打印出来。输出结果为: ``` 1 2 3 4 5 ``` 这样就实现了使用jsoncpp读入数组的功能。 ### 回答2: 使用jsoncpp读取数组可以分为以下几个步骤: 1. 引入jsoncpp库,可以从官方网站https://github.com/open-source-parsers/jsoncpp下载源码,并进行编译安装。 2. 在代码中包含json.h头文件。 3. 定义一个Json::Value对象来存储读入的JSON数据。 4. 使用Json::Reader对象来解析json字符串并存储到Json::Value对象中。 下面是一个简单的示例代码: ```cpp #include <json/json.h> #include <iostream> #include <fstream> #include <string> int main() { std::ifstream file("data.json"); // 打开json文件 Json::Value root; // 定义一个Json::Value对象 Json::Reader reader; // 定义一个Json::Reader对象 if (!reader.parse(file, root)) { // 解析json文件 std::cout << "解析错误!" << std::endl; return 1; } file.close(); // 关闭文件 Json::Value arrayValue = root["array"]; // 获取json中的数组 if (!arrayValue.isArray()) { // 判断是否为数组 std::cout << "不是一个有效的数组!" << std::endl; return 1; } for (int i = 0; i < arrayValue.size(); i++) { // 遍历数组 std::cout << "数组元素" << i << ": " << arrayValue[i].asString() << std::endl; } return 0; } ``` 假设data.json文件的内容为: ```json { "array": [ "元素1", "元素2", "元素3" ] } ``` 上述代码将会输出: ``` 数组元素0: 元素1 数组元素1: 元素2 数组元素2: 元素3 ``` 这样就完成了使用jsoncpp读取数组的操作。 ### 回答3: JSONCpp是一个用于处理JSON格式数据的C++库。它可以读取JSON格式的数据,并提供了相应的方法和函数来解析和访问JSON中的数据。 要读入一个JSON数组,可以按照以下步骤进行操作: 1. 首先,引入JSONCpp的头文件和命名空间: ```cpp #include <json/json.h> using namespace Json; ``` 2. 创建一个`Value`对象来保存JSON数据: ```cpp Value root; ``` 3. 使用`Reader`类的`parse`方法来解析JSON数据,并将解析结果保存到`Value`对象中: ```cpp Reader reader; if (!reader.parse(jsonData, root)) { // 解析失败 } ``` 其中,`jsonData`是一个字符串,用于存储JSON数据。 4. 确认解析结果是否为数组类型,若不是数组类型则表示解析失败: ```cpp if (!root.isArray()) { // JSON数据不是数组类型 } ``` 5. 遍历数组,可以使用循环来逐个访问数组的元素: ```cpp for (unsigned int i = 0; i < root.size(); i++) { // 访问数组元素 Value element = root[i]; // 对元素进行处理... } ``` 通过以上步骤,就可以成功读取JSON数组中的数据了。注意需要根据实际情况进行错误处理和数据类型的判断,以确保程序能正确地读取和处理JSON数组
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值