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
相关产品推荐
相关产品推荐

