项目场景:

之前为了方便 SDDC 协议使用,我自己写了一个 SDDC 的 SDK, 具体详见:同人逼死官方系列!基于sddc 协议的SDK框架 sddc_sdk_lib 解析 和 同人逼死官方系列 从 DDC 嗅探器到 sddc_sdk_lib 的数据解析 ,但是当时对 cjson 的使用还不是很熟悉,导致出现了一个内存泄露的问题,导致了ESP32运行一段时间后无法收到报文。


问题描述

在频繁收到消息命令,调用 sddc_on_message_lib 函数后,大概半小时左右 ESP32 将会收不到任何报文,通过打印发现 select 一直返回 0 ,这个问题一度让我非常费解。
网上最多的回答是 FD_SET 没有重置导致的,可是我检查了 sddc 代码,官方确实做了循环调用 FD_SET ,没有问题,不是这个原因。
射频信号确实发出来了。
ESP32底层也不应该有问题。

当时出现问题的是一个 I2C设备。导致我一直在怀疑是 I2C 导致的问题,别说还真有可能,ESP32 的 I2C 有可能会受到干扰 I2C 《 调试经验分享》然后我苦兮兮的去检测 I2C ,发现也是正常的。这就很令人费解了啊。

最后我无意中发现另外一个 通过 GPIO 读取数据的设备在长时间运行后也出现这样的问题。我对比了这两个设备和其他没问题的设备,最后发现差异在触发 "get" 命令的频率上,而 "get" 是一个命令,会触发消息处理回调函数,这样我才把注意力调到了回调处理流程上,然后发现了一处内存泄漏  ̄□ ̄||。

esp32c3 jtag 打印 esp32 cjson_Parse


原因分析:

内存泄露位于 SDDC_SDK_lib.c 中的 sddc_on_message_lib ,里面调用的 cJSON_Parse 函数会在次级函数中申请内存给root,需要手动释放一下。

(1)使用root = cJSON_Parse(text); //将文本转成json格式,次函数里面申请了一块内存给root
所以在最后要释放root

cJSON_Delete(root ); //释放cJSON_Parse()分配出来的内存空间

(2)使用str =
cJSON_Print(root);//次函数将json数据转成字符串,这个函数内申请了一段内存给out,所以使用完out后也要释放

(3)使用cJSON *new_json_str =
cJSON_CreateString(str);//将一个字符串转成一个json对象,函数里面也涉及了内存分配,座椅用完以后也要释放cJSON_Delete(new_json_str

需要注意:cJSON_CreateObject创建的指针,需要使用cJSON_Delete删除,cJSON_Print赋值的指针需要free释放。对应不上是没办法真正释放的。


解决方案:

修改后的 sddc_on_message_lib 代码如下:其实就是在每一个出口处加了一个 cJSON_Delete(root); 函数释放内存。

static sddc_bool_t sddc_on_message_lib(sddc_t *sddc, const uint8_t *uid, const char *message, size_t len)
{
    cJSON   *root    = cJSON_Parse(message);
    cJSON   *Json_method;
    uint8_t uimethod =DDC_METHOD_VALIDE;

    Json_method = cJSON_GetObjectItem(root, "method");
    if (NULL == Json_method) {
		cJSON_Delete(root);                                         
        return SDDC_FALSE;
    }

    if (cJSON_IsString(Json_method)) {
        if (strcmp(Json_method->valuestring,"set") == 0) {
            uimethod = DDC_METHOD_SET;
        } else if (strcmp(Json_method->valuestring,"get") == 0) {
            uimethod = DDC_METHOD_GET;
        } else {
			cJSON_Delete(root);
            return SDDC_FALSE;
        }
    } else {
		cJSON_Delete(root);
        return SDDC_FALSE;
    }

    if (uimethod == DDC_METHOD_VALIDE) {
		cJSON_Delete(root);
        return SDDC_FALSE;
    }

    if (uimethod == DDC_METHOD_SET) {
        int i;

        /*
         *  数字量、显示量先查询设置,防止开关量是设备的使能
         */
        for (i=0; i<G_config->num_dev_reg_num; i++) {
            object_Number_Set(root, G_config->num_dev_reg[i].objname, G_config->num_dev_reg[i].Num_Fun);
        }

        for (i=0; i<G_config->dis_dev_num; i++) {
            object_Display_Set(root, G_config->dis_dev_reg[i].objname, G_config->dis_dev_reg[i].Dis_Fun);
        }

        for (i=0; i<G_config->io_dev_reg_num; i++) {
            object_IO_Set(root, G_config->io_dev_reg[i].objname, G_config->io_dev_reg[i].IO_Fun);
        }
    } else if (uimethod == DDC_METHOD_GET) {
        object_get(sddc, uid, root, G_config->state_get_reg, G_config->state_get_reg_num, 0);
    } else {
		cJSON_Delete(root);
        return SDDC_FALSE;
    }
	
	cJSON_Delete(root);
    return SDDC_TRUE;
}