使用cJSON_Parse()解析libcurl长响应时偶现对象损坏问题
问题分析与解决方案
核心症状梳理:
- 调用API获取大体积响应时,在libcurl的write回调中用
cJSON_Parse()解析后,cJSON_Print()偶发输出异常字符串;小体积响应无此问题 - 直接打印回调的
data参数始终能得到完整响应,开启内存检查工具(如Valgrind)后异常消失
最可能的根源与修复方案
1. 未正确拼接libcurl分块返回的响应数据
libcurl针对大体积响应会分多次触发write回调返回数据片段,如果直接用单次回调的data调用cJSON_Parse(),相当于每次只解析响应的一部分,必然得到异常的JSON结构,最终打印出奇怪内容。小响应可能刚好一次回调就返回完整数据,因此不会触发问题。
修复:
- 在回调外部维护一个动态缓冲区,每次回调将
data内容追加到缓冲区末尾 - 等
curl_easy_perform()执行完成(所有数据接收完毕),再对完整缓冲区调用cJSON_Parse() - 必须给缓冲区末尾添加
'\0'终止符,因为libcurl返回的data不带终止符
示例代码片段:
// 定义结构体保存缓冲区和当前长度 typedef struct { char *buffer; size_t size; } ResponseBuffer; // libcurl write回调函数 size_t write_callback(void *data, size_t size, size_t nmemb, void *userp) { ResponseBuffer *resp = (ResponseBuffer *)userp; size_t real_size = size * nmemb; // 扩容缓冲区,预留终止符位置 char *tmp = realloc(resp->buffer, resp->size + real_size + 1); if (!tmp) { // 内存分配失败处理,返回0告知libcurl中断请求 return 0; } resp->buffer = tmp; // 追加新数据到缓冲区 memcpy(resp->buffer + resp->size, data, real_size); resp->size += real_size; // 添加字符串终止符 resp->buffer[resp->size] = '\0'; return real_size; } // 主逻辑示例 int main() { ResponseBuffer resp = {NULL, 0}; CURL *curl = curl_easy_init(); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, "your_target_api_url"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); CURLcode res = curl_easy_perform(curl); if (res == CURLE_OK) { // 所有数据接收完成后再解析JSON cJSON *json = cJSON_Parse(resp.buffer); if (json) { char *print_str = cJSON_Print(json); if (print_str) { printf("%s\n", print_str); free(print_str); } cJSON_Delete(json); } } curl_easy_cleanup(curl); free(resp.buffer); } return 0; }
2. 未添加字符串终止符导致解析异常
即使拼接了数据,若不给缓冲区加'\0',cJSON_Parse()会持续读取内存直到遇到随机的'\0',这会导致解析出错误的JSON结构,引发偶发异常。大响应更容易触发此问题,因为内存后续的随机字符更可能干扰解析。
修复:严格按照上述示例,每次追加数据后都给缓冲区设置终止符。
3. 内存越界引发的未定义行为
开启内存检查工具后异常消失,说明可能存在内存越界(比如缓冲区扩容时计算错误、使用已释放的内存)。内存检查工具会拦截这类越界操作,或让内存布局更规整,从而掩盖问题。
排查与修复:
- 用Valgrind运行程序,查看是否有
Invalid write或Invalid read的提示,定位越界位置 - 检查缓冲区扩容的计算逻辑,确保
realloc的大小是当前size + 新数据size + 1(+1用于存放终止符) - 确认
cJSON_Delete()已正确调用,避免内存泄漏或野指针
验证步骤
- 先将解析逻辑从write回调移到所有数据接收完成后,验证是否解决问题
- 检查缓冲区是否始终以
'\0'结尾 - 用Valgrind扫描内存问题,修复所有越界或泄漏点
内容的提问来源于stack exchange,提问作者alfredapri
相关产品推荐
相关产品推荐

