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

cJSON_Duplicate失效无法生成预期JSON字符串问题排查求助

问题描述

需要创建包含多个列表条目的JSON字符串,实际场景中通过定时器ISR填充条目(示例用for循环模拟),但调用cJSON_Duplicate()和cJSON_Print()后无法得到预期的JSON字符串,同时add_list_entry()也会在定时器ISR的临界区内调用,需排查原因并给出修复方案。

示例代码
void add_list_entry(cJSON *parent, int index, uint32_t x, uint32_t z, uint32_t t_h, uint64_t t_us) {
    // 创建代表列表项的对象
    cJSON *entry = cJSON_CreateObject();
    cJSON_AddNumberToObject(entry, "X", x);
    cJSON_AddNumberToObject(entry, "Z", z);
    cJSON_AddNumberToObject(entry, "t_h", t_h);
    cJSON_AddNumberToObject(entry, "t_us", (double)t_us);

    // 创建用于存储单个条目的数组
    cJSON *array = cJSON_CreateArray();
    cJSON_AddItemToArray(array, entry);

    // 将数组添加到父对象,使用递增索引作为字符串键
    char key[11];  // 足以存储最大40亿的整数
    snprintf(key, sizeof(key), "%d", index);
    cJSON_AddItemToObject(parent, key, array);
}

void json_simulate(void)
{
    cJSON *root = cJSON_CreateObject();  // 根应为对象
    char *json_output;
    int i = 0;
    for (i = 0; i < 200; i++) {
        add_list_entry(root,       // 根JSON句柄
                       i,           // 每个条目的元素计数器
                       TIM1->CNT,   // X编码器输入
                       m_curr.dist, // Z距离
                       m_curr.t_h,  // 秒级时间
                       TIM5->CNT);  // 微秒定时器
    }
    taskENTER_CRITICAL();
    if (m_jsonroot != NULL) {
        cJSON_Delete(m_jsonroot);  // 确保删除之前的数据(如果存在)
    }
    m_jsonroot = cJSON_Duplicate(root, 1); // 复制到全局JSON句柄
    m_tx_msg = 1;                  // 设置全局更新标志
    json_output = cJSON_Print(m_jsonroot);
    taskEXIT_CRITICAL();
    free(json_output);             // 释放cJSON_Print()的结果
    cJSON_Delete(root);            // 删除本地根对象
}
问题分析与修复方案

核心问题点

  1. 并发访问冲突
    实际场景中ISR和json_simulate()任务会同时操作cJSON对象,但当前仅在复制m_jsonroot时加了临界区,add_list_entry()在ISR中的调用以及模拟代码的for循环修改root时未做互斥保护。cJSON的内部结构并非线程安全,并发修改会破坏链表或节点结构,导致复制、打印出的JSON损坏。

  2. JSON结构设计冗余(不符合预期)
    每个列表项被包裹在单独的数组中,最终JSON结构为{"0": [{"X": ...}], "1": [{"X": ...}], ...},如果预期是一个包含所有条目的数组(如[{"X": ...}, {"X": ...}, ...]),这种设计完全不符合需求,会导致输出结构与预期不符。

  3. uint64_t转double的精度丢失
    uint64_t类型的最大值超过了double的53位有效精度范围,强制转换后会丢失低位数据,导致t_us字段的值不准确。

  4. 复制时机的线程安全问题
    调用cJSON_Duplicate()时,如果root正被ISR修改,复制过程会读取到不完整或损坏的节点数据,导致m_jsonroot结构异常。

修复方案

1. 解决并发访问问题

  • 全局互斥保护:所有修改cJSON对象的操作(包括ISR中的add_list_entry()、json_simulate()中的for循环)都必须在临界区内执行,确保同一时间只有一个上下文(任务/ISR)操作JSON结构:
    • 在ISR中调用add_list_entry()前,使用taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()包裹;
    • 模拟代码的for循环也需要放到临界区内,避免与ISR并发修改root。
  • 双缓冲机制(更优):准备两个独立的cJSON根对象,ISR固定往其中一个写,json_simulate()任务复制另一个的内容,复制完成后切换缓冲区,避免临界区长时间占用影响ISR响应。

2. 修正JSON结构

如果预期是一个包含所有条目的数组,调整代码如下:

// 修改add_list_entry,直接将条目添加到父数组
void add_list_entry(cJSON *parent, uint32_t x, uint32_t z, uint32_t t_h, uint64_t t_us) {
    cJSON *entry = cJSON_CreateObject();
    cJSON_AddNumberToObject(entry, "X", x);
    cJSON_AddNumberToObject(entry, "Z", z);
    cJSON_AddNumberToObject(entry, "t_h", t_h);
    
    // 用字符串存储uint64_t,避免精度丢失
    char t_us_str[21];
    snprintf(t_us_str, sizeof(t_us_str), "%llu", t_us);
    cJSON_AddStringToObject(entry, "t_us", t_us_str);
    
    cJSON_AddItemToArray(parent, entry);
}

// 修改json_simulate的root为数组
void json_simulate(void)
{
    cJSON *root = cJSON_CreateArray();
    char *json_output;
    int i = 0;

    taskENTER_CRITICAL();
    for (i = 0; i < 200; i++) {
        add_list_entry(root,
                       TIM1->CNT,
                       m_curr.dist,
                       m_curr.t_h,
                       TIM5->CNT);
    }
    if (m_jsonroot != NULL) {
        cJSON_Delete(m_jsonroot);
    }
    m_jsonroot = cJSON_Duplicate(root, 1);
    m_tx_msg = 1;
    json_output = cJSON_Print(m_jsonroot);
    taskEXIT_CRITICAL();

    free(json_output);
    cJSON_Delete(root);
}

3. 修复uint64_t精度问题

将uint64_t类型的t_us转换为字符串存储,避免用double类型导致的精度丢失,如上述代码中使用snprintf()将t_us转为字符串后添加到JSON对象。

4. 确保复制操作的原子性

cJSON_Duplicate()必须在临界区内执行,同时保证在复制过程中没有其他上下文修改源对象root,避免读取到不一致的节点数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 12:22:22