C语言调用json-c的json_object_get_string出现内存泄漏问题
问题根因
首先纠正认知偏差:json_object_get_string接口本身不会产生需要用户手动释放的内存,它返回的是json-c库内部维护的字符串指针,生命周期和所属的json_object实例绑定,一旦调用json_object_put将对象引用计数减到0,这个指针就会成为野指针,你用strdup做字符串拷贝的操作本身是正确的。
你遇到的内存泄漏是代码本身的逻辑缺陷导致的,和json-c接口无关,核心问题有三个:
- 分支资源泄漏:当json文件解析失败、或者字段校验不匹配时,你没有调用
json_object_put释放json_object_from_file返回的根对象,每次循环走到异常分支都会泄漏一整个json解析后的对象树。 - 缺失自定义内存释放逻辑:你通过
strdup为结构体成员分配的堆内存,在结构体生命周期结束后没有配套的释放逻辑,valgrind报的泄漏绝大多数来自这部分未释放的自分配内存。 - 逻辑笔误:原代码中
examples[i] = container属于未定义变量错误,且当循环中出现字段校验不通过的场景时,循环变量i和实际有效元素计数count不匹配,直接用i做数组索引会出现内存越界。
你之前尝试存入字符串后立刻free指针导致值丢失的原因很简单:strdup返回的是堆内存的地址,你把地址存在结构体里,立刻free相当于把这块内存的所有权归还给操作系统,结构体里存的地址变成野指针,自然读不到有效内容——你存的是内存地址,不是字符串的拷贝,释放地址指向的内存后内容就不存在了。
修复方案
首先修正list函数的逻辑漏洞,同时补充配套的资源释放函数,所有从list拿到的结构体实例,用完后必须调用释放函数回收内存。
修正后的完整代码如下:
struct Example { char *id; char *name; char *logPath; }; struct Examples { struct Example *examples; size_t size; }; // 提前声明释放函数,供异常分支回滚调用 void free_examples_list(struct Examples list); struct Examples list() { size_t i; size_t count = 0; struct Example *examples = NULL; struct Examples examplesList = {.examples = NULL, .size = 0}; for (i = 0; i < 59; i++) { json_object *root_obj = json_object_from_file("path.json"); // 解析失败直接跳过,避免空指针访问 if (root_obj == NULL) { continue; } json_object *jID; json_object *jName; json_object *jLogPath; // 字段校验不通过时先释放根对象,再进入下一轮循环 if (json_object_object_get_ex(root_obj, "Name", &jName) != TRUE || json_object_object_get_ex(root_obj, "LogPath", &jLogPath) != TRUE || json_object_object_get_ex(root_obj, "ID", &jID) != TRUE) { json_object_put(root_obj); continue; } char *id = strdup(json_object_get_string(jID)); char *name = strdup(json_object_get_string(jName)); char *logPath = strdup(json_object_get_string(jLogPath)); // 拿到字符串拷贝后立刻释放json对象,缩短对象生命周期 json_object_put(root_obj); // 处理strdup分配失败的异常场景,回滚已分配资源 if (id == NULL || name == NULL || logPath == NULL) { free(id); free(name); free(logPath); free_examples_list(examplesList); die("Strdup alloc failed"); } count++; struct Example *tmpExamples = realloc(examples, count * sizeof(struct Example)); if (tmpExamples == NULL) { free(id); free(name); free(logPath); free_examples_list(examplesList); die("Realloc failed"); } examples = tmpExamples; // 用实际有效元素计数做索引,避免越界 examples[count - 1] = (struct Example){ .id = id, .name = name, .logPath = logPath }; examplesList.examples = examples; examplesList.size = count; } return examplesList; } // 配套的资源释放函数 void free_examples_list(struct Examples list) { if (list.examples == NULL) { return; } for (size_t idx = 0; idx < list.size; idx++) { // 逐个释放strdup分配的字符串 free(list.examples[idx].id); free(list.examples[idx].name); free(list.examples[idx].logPath); } // 释放示例数组本身 free(list.examples); }
注意事项
- json-c库的内存管理遵循引用计数规则:所有通过
json_object_from_file、json_object_new_*接口创建的对象,最终必须配对调用json_object_put,不要手动释放json_object_get_string返回的内部指针,库会在对象销毁时自动回收这部分内存。 - C语言中所有通过
strdup、malloc、calloc、realloc分配的堆内存,必须在确定不再使用后调用free回收,跨函数传递内存时要明确所有权归属,避免出现只分配不释放的泄漏问题。
内容的提问来源于stack exchange,提问作者jcarlosweb
相关产品推荐
相关产品推荐

