欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C语言 cJSON cJSON_Delete 与 cJSON_Free 的区别、如何释放内存、调试内存泄漏方法

程序员文章站 2024-03-18 09:15:34
...

cJSON Delete与free的区别、如何释放内存、调试内存泄漏方法

V0.0.4.20210624


项目简介

cJSON是目前C语言主流的JSON格式处理开源库

cJSON仓库地址:https://github.com/DaveGamble/cJSON

  • cJSON文章使用的版本:

    [[email protected] cJSON]$ git log --oneline 
    324a6ac (HEAD -> master, origin/master, origin/HEAD) Update .gitattributes (#544)
    6ea4c01 Fix potential core dumped for strrchr (#546)
    9226e4e Remove always true condition in cJSON.c (#539)
    
    

cJSON释放内存前言

cJSON* json_raw = cJSON_Parse(mess);
cJSON* json_res = cJSON_CreateObject();

​ cJSON最常用的就像这样的用法,从字符串解析一个cJSON结构,或者生成一份cJSON结构用以后续生成字符串

这两个用法都会导致cJSON在解析或生成一个json元素时候调用malloc函数申请内存,如果在使用cJSON后没有释放内存,这些内存会一致留在程序的堆空间,造成内存泄漏

两个释放内存的函数 cJSON_Delete cJSON_Free

cJSON_Free

  • 先看源码,cJSON.c的最后一个函数

    CJSON_PUBLIC(void) cJSON_free(void* object)
    {
        global_hooks.deallocate(object);
    }
    
  • 全局变量 global_hooks

    ...
    /* 177行 */
    #else
    #define internal_malloc malloc
    #define internal_free free
    #define internal_realloc realloc
    #endif
    ...
    
    /* 186行 */
    static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc };
    

    默认cJSON用来管理内存的方法存储在这个全局变量中

  • cJSON_Free 分析

    • 函数只有一行,仅仅时调用free函数释放了传入 cJSON_Free 的指针
  • 用途

    • 不适合用来释放一个cJSON结构的真个json解析结果,因为cJSON结构体只存储一个json元素,使用next指针指向下一个结构,next节点将会变成孤立的节点。一次使用free仅可以释放一个json元素结构,除非你确定这个json也只有一个元素

    • 适合用来修改cjson结构,用以裁切,对一个不需要的cjson !!!最尾端的叶子节点!!!使用这个函数,并将它的父节点、兄弟节点的next和child指针为NULL,但不推荐这种用法,如果需要删除一个节点,可以直接调用删除节点的函数

    • 适合用来删除 cJSON_Print 函数生成的字符串,这个函数生成的字符串指针也是需要释放的,可以使用 cJSON_Free 释放,因为这个字符串指针是孤立的

      /**
      *	.h 102行 
      *	可以用来删除一个节点,但是不建议,有现成的函数用来删除节点
      */
      /* The cJSON structure: */
      typedef struct cJSON
      {
          /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
          struct cJSON *next;		// 删除子节点时修改大表哥节点的这里,同时本节点这里需要为NUL
          struct cJSON *prev;		// 删除子节点时修改父亲节点的这里
          /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
          struct cJSON *child;	// 删除时本节点需要没有子节点
      
          /* The type of the item, as above. */
          int type;
      
          /* The item's string, if type==cJSON_String  and type == cJSON_Raw */
          char *valuestring;
          /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
          int valueint;
          /* The item's number, if type==cJSON_Number */
          double valuedouble;
      
          /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
          char *string;
      } cJSON;
      

cJSON_Delete

  • 源码

    /* .c 252行 */
    /* Delete a cJSON structure. */
    CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
    {
        cJSON *next = NULL;
        while (item != NULL)
        {
            next = item->next;
            if (!(item->type & cJSON_IsReference) && (item->child != NULL))
            {
                cJSON_Delete(item->child);
            }
            if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
            {
                global_hooks.deallocate(item->valuestring);
            }
            if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
            {
                global_hooks.deallocate(item->string);
            }
            global_hooks.deallocate(item);
            item = next;
        }
    }
    
  • 分析

    • delete 函数会分析传入的类型是什么类型的JSON结构,并会递归删除所有的节点
    • delete 如果传入的不是json结构,如 cJSON_Print 生成的字符串可能会导致指针指向时出现意想不到的错误,不建议这样做,即使你的编译器没有报错
  • 用途

    • 用以删除一整个json结构,会将所有的节点全部释放内存
    • 须从整个json的根节点开始调用,否则会造成这个节点的父节点和兄弟节点仍保存这个被释放的结构地址,然而这些地址已不再使用,可能导致意想不到的错误

如何释放内存的方法总结

  • 释放整个json结构
    • 使用 cJSON_Delete 释放这个json结构的根节点
    • 必须释放的的时这个结构的根节点,详见上面
  • 释放json结构的某些节点
    • 使用其他函数删除,而不是这两个,有现成的 delete 节点函数
    • 如果没有,使用 delete 删除节点或使用 free 删除 最尾端的叶子节点,并注意修改父节点和兄弟节点的child、next指针为NULL
  • 释放 cJSON_Print函数生成的字符串
    • 使用 cJSON_Free 释放即可,推荐
    • 偷懒使用 cJSON_Delete 释放可能编译器不会报错,但是因为 Delete 会使用 cJSON* 指向这个内存使用,可能会出现意想不到的错误,尤其是这段字符串又很短时

调试内存泄漏方法

  • 统计malloc 和 free 的次数,如果一致则没有内存泄漏,realloc不必统计,失败和成功不影响

  • 源码修改

    ...
    /* 177行 */
    /***/
    #else
    #define internal_malloc (agent_malloc)
    #define internal_free (agent_free)
    #define internal_realloc (agent_realloc)
    #endif
    
    /* 186行 */
    static internal_hooks global_hooks = { agent_malloc, agent_free, agent_realloc };
    
  • 统计次数的源代码,程序即将结束时打印 agent_dbg_mem_time 的值即可,如果该值是0表示申请和释放内存次数一样,没有发生泄漏,如果大于0就需要排查下哪些地方没有正确处理释放内存了

    static int dbg_malloc_time = 0;
    
    void* agent_malloc(size_t size)
    {
        void* mem = malloc(size);
        if(mem != NULL)
            dbg_malloc_time++;
        else
            dbg_printf("false, %ld", size);
        return mem;
    }
    
    void* agent_realloc(void* mem, size_t size)
    {
        void* new_mem = realloc(mem, size);
        return new_mem;
    }
    
    void agent_free(void* mem)
    {
        free(mem);
        dbg_malloc_time--;
    }
    
    int agent_dbg_mem_time(void)
    {
        return dbg_malloc_time;
    }
    
  • 原理

    • 替换(重载)cJSON中关于内存申请释放的地方
      • 注:cJSON还有其他地方用到了申请和释放内存,不过通常没有用到,如果有需求可以全部替换
  • 效果预览

    • 最后的值是0,该程序中大概malloc了近百次,你也可以尝试打印下这个值出现过的最大值,确保代码是有效的
    agent.c,	main,63 >> mem end 0
    

使用说明

维护说明

注意

关于作者

Autho: KiraSkyler
Email: [email protected] / [email protected]

贡献者/贡献组织

鸣谢

版权信息

该项目签署了GPL 授权许可,详情请参阅官网

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses/.

更新日志

  • V0.0.0.20210101