You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言向回调函数传递字符数组的正确方法及问题排查

问题根因

两个版本的代码分别踩了嵌入式异步C编程的两个典型错误:

  • 栈局部变量版本乱码:nonceHex是分配在protothread栈上的临时变量,mg_connect_http是全异步接口,调用时仅保存传入的字符串指针,不会拷贝字符串内容。等HTTP响应返回触发回调时,原栈内存早就被后续的函数调用覆写,打印出来自然是乱码。用static修饰数组也不能解决问题:500ms轮询间隔下如果前一个请求超时未完成,下一轮循环就会覆写static内存区,回调拿到的还是错误值。
  • malloc版本触发内核panic:核心原因是内存释放时机完全错误。你仅在MG_EV_HTTP_REPLY事件分支释放了内存,但Mongoose的连接回调会在整个连接生命周期触发十多种事件,MG_EV_HTTP_REPLY触发时连接并未关闭,后续框架还会多次携带cb_arg指针触发其他事件(比如数据接收完成、连接关闭等),访问已经释放的野指针就会触发LoadProhibited异常。另外如果遇到DNS解析失败、连接拒绝、请求超时,根本不会触发MG_EV_HTTP_REPLY事件,既会造成内存泄漏,后续事件访问野指针同样会导致崩溃。
正确实现方案

核心要遵守两个原则:

  • 传给异步接口的用户数据,生命周期必须覆盖整个连接从创建到关闭的全流程
  • 用户数据的内存释放必须放在连接最终关闭的统一事件点,不能只在成功响应的分支释放

轮询线程代码修改

static int polling_thread(struct pt *pt, uint16_t ticks)
{
    static uint16_t timer;
    timer += ticks;

    PT_BEGIN(pt);

    while (1)
    {
        uint8_t nonce[NONCE_LEN] = {};
        // 用calloc分配内存,自动初始化为0,不需要手动补字符串终止符
        // 长度为2*NONCE_LEN(十六进制编码长度) + 1(字符串结束符)
        char *nonceHex = calloc(2 * NONCE_LEN + 1, sizeof(char));
        if (nonceHex == NULL) {
            // 内存分配失败的容错处理,跳过本次轮询
            timer = 0;
            PT_WAIT_UNTIL(pt, timer >= 500);
            continue;
        }
        RNG(nonce, NONCE_LEN);
        hex_encode(nonce, sizeof(nonce), nonceHex);
        
        struct mg_connection *c = mg_connect_http(mgos_get_mgr(), http_cb, nonceHex, url, (const char *)sEtagHeader, NULL);
        if (c == NULL) {
            // 连接创建失败不会触发回调,直接释放内存避免泄漏
            free(nonceHex);
        }

        timer = 0;
        PT_WAIT_UNTIL(pt, timer >= 500);
    }

    PT_END(pt);
}

回调函数代码修改

在MG_EV_CLOSE事件中统一释放内存——不管是请求成功、失败、超时、主动断开,Mongoose在连接销毁前一定会触发一次该事件,是释放用户数据的唯一安全位置:

static void http_cb(struct mg_connection *nc, int ev, void *evd, void *cb_arg)
{
    const char *nonceHex = (const char *)cb_arg;
    switch (ev)
    {
        case MG_EV_HTTP_REPLY:
        {
            LOG(LL_DEBUG, ("http_cb nonce: %s", nonceHex));
            struct http_message *hm = (struct http_message *)evd;
            // 这里写你的响应处理业务逻辑即可,绝对不要提前free nonceHex
            
            // 处理完成后如果不需要保持长连接,标记关闭连接即可
            nc->flags |= MG_F_CLOSE_IMMEDIATELY;
            break;
        }
        case MG_EV_CLOSE:
        {
            // 所有连接退出路径都会走到这里,统一释放内存
            if (nonceHex != NULL) {
                free((void *)nonceHex);
                nc->user_data = NULL;
            }
            LOG(LL_DEBUG, ("connection closed, nonce buffer freed"));
            break;
        }
        // 其他事件(连接失败、超时等)按需处理,不要操作nonceHex的生命周期
        default:
            break;
    }
}

额外注意事项

  • 不要在MG_EV_HTTP_REPLY事件中释放用户数据:该事件触发后连接还未完成销毁,框架后续仍可能访问cb_arg指针,提前释放必然造成野指针错误。
  • 尽量用calloc代替malloc分配字符串内存:嵌入式场景下很多hex_encode类工具函数不会主动写字符串终止符,calloc自动清零的特性可以避免字符串越界导致的乱码问题。
  • 连接创建失败、内存分配失败的分支一定要做对应的内存释放处理,否则设备长时间运行会出现内存泄漏,最终导致系统崩溃。

内容的提问来源于stack exchange,提问作者Marko Seidenglanz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 21:21:36