ESP32 ESP-IDF中cJSON、缓冲区及数据结构的动态内存分配问题
解决ESP32 ESP-IDF开发中的内存分配与cJSON解析问题
一、动态内存分配读取文件
原读取函数存在文件打开失败未处理、读取长度错误、未动态分配内存等问题。正确实现需通过fseek获取文件大小,用malloc分配对应内存,读取后添加字符串终止符并关闭文件。
修正后的读取函数:
#define TAG "FILE" char *read(const char *path) { FILE *f = fopen(path, "r"); if (f == NULL) { ESP_LOGE(TAG, "Could not open file for reading"); return NULL; } // 获取文件大小 fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); // 动态分配内存,额外+1存储字符串终止符 char *buffer = (char *)malloc(size + 1); if (buffer == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for file buffer"); fclose(f); return NULL; } // 读取文件内容 size_t read_len = fread(buffer, 1, size, f); if (read_len != size) { ESP_LOGE(TAG, "Failed to read entire file, read %zu bytes out of %ld", read_len, size); free(buffer); fclose(f); return NULL; } // 添加字符串终止符,确保cJSON能正确解析 buffer[size] = '\0'; fclose(f); return buffer; }
二、修复cJSON解析的内存断言错误
触发assert failed: tlsf_free tlsf.c:1120的核心原因:
- 循环内重复调用
cJSON_Delete(json),第一次循环就释放了根节点,后续循环访问已释放内存导致重复free; cJSON_Parse(*buffer)传入参数错误,应直接传buffer字符串指针而非单个字符;json == NULL时调用cJSON_Delete(json)会触发未定义行为。
修正后的JSON解析函数:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "cJSON.h" #include "esp_log.h" #define TAG "JSON" typedef struct caststruct{ int value1; int value2; int value3; int value4; int value5; } CastStruct; // 动态分配结构体数组,避免固定大小限制 CastStruct *sdata = NULL; int sdata_count = 0; void jsonread(){ char filename[] = "/test1"; ESP_LOGI(TAG,"Step1: Reading file"); char *buffer = read(filename); if (buffer == NULL) { ESP_LOGE(TAG, "Failed to load file"); return; } ESP_LOGI(TAG,"Step2: File loaded, size: %zu", strlen(buffer)); // 解析JSON,传入buffer指针而非*buffer cJSON *json = cJSON_Parse(buffer); if (json == NULL) { const char *error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { ESP_LOGE(TAG, "Parse error: %s", error_ptr); } free(buffer); return; } ESP_LOGI(TAG,"Step3: JSON parsed successfully"); // 获取数组元素数量,动态分配结构体数组 sdata_count = cJSON_GetArraySize(json); sdata = (CastStruct *)malloc(sizeof(CastStruct) * sdata_count); if (sdata == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for sdata"); cJSON_Delete(json); free(buffer); return; } int i = 0; cJSON *iter; cJSON_ArrayForEach(iter, json){ ESP_LOGI(TAG,"Processing array element %d", i); cJSON *valuex = cJSON_GetObjectItemCaseSensitive(iter,"valuex"); cJSON *valuei = cJSON_GetObjectItemCaseSensitive(iter,"valuei"); // 确保字段存在且为数字 if(cJSON_IsNumber(valuex) && cJSON_IsNumber(valuei)) { sdata[i].value1 = cJSON_GetNumberValue(valuex); sdata[i].value2 = 34; sdata[i].value3 = cJSON_GetNumberValue(valuei); i++; } // 注意:不要在这里调用cJSON_Delete(json)! } // 打印解析结果 for(int j=0 ; j<sdata_count;j++) { ESP_LOGI(TAG,"value1[%d] = %d", j, sdata[j].value1); ESP_LOGI(TAG,"value2[%d] = %d", j, sdata[j].value2); ESP_LOGI(TAG,"value3[%d] = %d", j, sdata[j].value3); } // 释放所有动态分配的内存 cJSON_Delete(json); free(buffer); free(sdata); sdata = NULL; }
三、内存分配最佳实践与替代JSON解析器
内存分配要点
- Buffer:根据实际文件大小动态分配,避免全局大数组浪费内存;使用完成后必须调用
free释放。 - cJSON对象:根节点仅需在解析完成后调用一次
cJSON_Delete释放,循环内无需重复释放;遍历节点由根节点统一管理,无需单独释放。 - 结构体数组sdata:先获取JSON数组长度,再动态分配对应内存,避免固定大小限制;使用完成后调用
free释放。
ESP32可用的替代JSON解析器
- jsmn:ESP-IDF自带轻量级解析器,基于token解析,内存占用极低,无需动态内存分配,适合资源有限场景。
- esp-tinyjson:专为ESP32优化的小型JSON解析/生成库,API简单,内存占用小。
- nlohmann/json:C++风格JSON库,API易用,但编译后体积较大,适合内存充足、追求代码可读性的场景。
内容的提问来源于stack exchange,提问作者harryw
相关产品推荐
相关产品推荐

