正文
大家好,我是bug菌~
最近看到一个非常有意思的词语"回调地狱",其实就是无尽的回调函数,最终导致爆栈卡死,形式表现上就是多层嵌套的函数指针回调,导致代码可读性差、维护困难。
下面简单编了一个示例模拟异步读取文件后连续进行三次数据处理(每步依赖前一步结果):
#include <stdio.h>
#include <stdlib.h>
/* 第1步:读取文件内容 */
void read_file(const char* filename, void (*process1)(char*, void*), void* user_data);
/* 第2步:初步处理数据 */
void process_data1(char* data, void (*process2)(char*, void*), void* user_data);
/* 第3步:深度处理数据 */
void process_data2(char* data, void (*process3)(char*, void*), void* user_data);
/* 最终处理结果 */
void final_process(char* result, void* user_data);
/* 用户数据 */
struct Context {
int retry_count;
char* output_path;
};
int main() {
struct Context ctx = {3, "/path/to/output.txt"};
/* 回调地狱开始(三层嵌套)*/
read_file("input.txt",
(void(*)(char*, void*))process_data1, // 强制转换解决C89类型检查
(void*)&ctx
);
return0;
}
/* 模拟文件读取 */
void read_file(const char* filename, void (*process1)(char*, void*), void* user_data) {
char* mock_data = "Hello,Callback!";
printf("Read complete\n");
/* 第1层回调 */
process1(mock_data, user_data);
}
/* 第一步处理 */
void process_data1(char* data, void (*process2)(char*, void*), void* user_data) {
/* 模拟处理 */
char* processed = (char*)malloc(50);
sprintf(processed, "[1]%s", data);
printf("Process1 done: %s\n", processed);
/* 第2层回调 */
process2(processed, user_data);
free(processed); // 注意:实际开发需考虑内存生命周期
}
/* 第二步处理 */
void process_data2(char* data, void (*process3)(char*, void*), void* user_data) {
/* 模拟深度处理 */
char* final = (char*)malloc(50);
sprintf(final, "[2]%s", data);
printf("Process2 done: %s\n", final);
/* 第3层回调 */
process3(final, user_data);
free(final);
}
/* 最终处理 */
void final_process(char* result, void* user_data) {
struct Context* ctx = (struct Context*)user_data;
printf("Final output (%d retries): %s -> %s\n",
ctx->retry_count, result, ctx->output_path);
}
/* 需要手动链接回调(实际使用时) */
void process_data1(char*, void*, void*); // 前向声明调整
void process_data2(char*, void*, void*); // 解决依赖
/* 实际回调绑定(需要严格匹配) */
void process_data1(char* data, void (*next)(char*, void*), void* ctx) {
/* 包装为统一参数 */
void callback_adapter(char* d, void* c) {
next(d, c);
}
process_data1(data, callback_adapter, ctx); // 调用实际的process_data1
}
回调嵌套结构:
read_file
→ process_data1
→ process_data2
→ final_process
每层函数需要传递下一层的回调指针和用户数据
深层嵌套导致括号和缩进混乱
需要手动管理回调函数的参数匹配(通过
void*
类型转换)无法统一处理错误,需每层单独处理
内存管理复杂:跨回调的内存释放容易出错
最危险的是较大的调用深度非常消耗栈,导致溢出。
在我们日常开发中该如何规避呢?
我们可以将多层嵌套的回调转换为线性的状态转移流程,最常见的就是采用状态机的方式了。通过状态机管理处理流程,使代码结构更清晰、易维护:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 用户数据结构体 */
struct Context {
int retry_count;
char* output_path;
};
/* 状态枚举定义处理阶段 */
typedefenum {
STATE_READ_FILE, // 读取文件阶段
STATE_PROCESS1, // 初步处理阶段
STATE_PROCESS2, // 深度处理阶段
STATE_FINAL, // 最终处理阶段
STATE_DONE // 完成状态
} State;
/* 状态机结构体,维护处理状态和上下文 */
typedefstruct {
State current_state; // 当前状态
struct Context* ctx;// 用户上下文
char* current_data; // 当前处理的数据
} StateMachine;
/* 初始化状态机 */
StateMachine* state_machine_init(struct Context* ctx) {
StateMachine* sm = (StateMachine*)malloc(sizeof(StateMachine));
if (!sm) returnNULL;
sm->current_state = STATE_READ_FILE;
sm->ctx = ctx;
sm->current_data = NULL;
return sm;
}
/* 清理状态机资源 */
void state_machine_cleanup(StateMachine* sm) {
if (sm) {
free(sm->current_data); // 释放当前数据内存
free(sm); // 释放状态机本身
}
}
/* 状态处理函数:读取文件 */
static void handle_read_file(StateMachine* sm) {
// 模拟文件读取(实际可替换为真实文件读取逻辑)
constchar* mock_data = "Hello,Callback!";
printf("Read complete\n");
// 复制数据到状态机上下文
sm->current_data = strdup(mock_data);
if (!sm->current_data) {
fprintf(stderr, "Memory allocation failed in read phase\n");
sm->current_state = STATE_DONE; // 分配失败则终止
return;
}
sm->current_state = STATE_PROCESS1; // 转移到初步处理阶段
}
/* 状态处理函数:初步处理数据 */
static void handle_process1(StateMachine* sm) {
if (!sm->current_data) {
fprintf(stderr, "No data to process in process1\n");
sm->current_state = STATE_DONE;
return;
}
// 模拟初步处理(添加标记)
char* processed = (char*)malloc(50);
if (!processed) {
fprintf(stderr, "Memory allocation failed in process1\n");
sm->current_state = STATE_DONE;
return;
}
snprintf(processed, 50, "[1]%s", sm->current_data);
printf("Process1 done: %s\n", processed);
free(sm->current_data); // 释放旧数据
sm->current_data = processed; // 更新为新数据
sm->current_state = STATE_PROCESS2; // 转移到深度处理阶段
}
/* 状态处理函数:深度处理数据 */
static void handle_process2(StateMachine* sm) {
if (!sm->current_data) {
fprintf(stderr, "No data to process in process2\n");
sm->current_state = STATE_DONE;
return;
}
// 模拟深度处理(二次标记)
char* final = (char*)malloc(50);
if (!final) {
fprintf(stderr, "Memory allocation failed in process2\n");
sm->current_state = STATE_DONE;
return;
}
snprintf(final, 50, "[2]%s", sm->current_data);
printf("Process2 done: %s\n", final);
free(sm->current_data); // 释放旧数据
sm->current_data = final; // 更新为新数据
sm->current_state = STATE_FINAL; // 转移到最终处理阶段
}
/* 状态处理函数:最终处理结果 */
static void handle_final(StateMachine* sm) {
if (!sm->current_data) {
fprintf(stderr, "No data to finalize\n");
sm->current_state = STATE_DONE;
return;
}
// 调用最终处理函数(使用用户上下文)
final_process(sm->current_data, sm->ctx);
free(sm->current_data); // 释放最终数据
sm->current_data = NULL;
sm->current_state = STATE_DONE; // 标记为完成
}
/* 驱动状态机运行 */
void state_machine_run(StateMachine* sm) {
while (sm->current_state != STATE_DONE) {
switch (sm->current_state) {
case STATE_READ_FILE: handle_read_file(sm); break;
case STATE_PROCESS1: handle_process1(sm); break;
case STATE_PROCESS2: handle_process2(sm); break;
case STATE_FINAL: handle_final(sm); break;
default:
fprintf(stderr, "Invalid state!\n");
sm->current_state = STATE_DONE;
break;
}
}
}
/* 模拟文件读取(原逻辑保留) */
void read_file(const char* filename, void (*process)(char*, void*), void* user_data) {
// 此处实际应替换为文件读取逻辑,示例使用模拟数据
char* mock_data = "Hello,Callback!";
printf("Read complete\n");
process(mock_data, user_data);
}
/* 原处理函数(调整为独立功能) */
void process_data1(char* data, void* user_data) {
char* processed = (char*)malloc(50);
snprintf(processed, 50, "[1]%s", data);
printf("Process1 done: %s\n", processed);
free(processed); // 示例中直接释放(实际需根据需求调整)
}
void process_data2(char* data, void* user_data) {
char* final = (char*)malloc(50);
snprintf(final, 50, "[2]%s", data);
printf("Process2 done: %s\n", final);
free(final);
}
/* 最终处理结果(原逻辑保留) */
void final_process(char* result, void* user_data) {
struct Context* ctx = (struct Context*)user_data;
printf("Final output (%d retries): %s -> %s\n",
ctx->retry_count, result, ctx->output_path);
}
int main() {
struct Context ctx = {3, "/path/to/output.txt"};
// 使用状态机替代回调嵌套
StateMachine* sm = state_machine_init(&ctx);
if (!sm) {
fprintf(stderr, "Failed to initialize state machine\n");
return1;
}
state_machine_run(sm); // 运行状态机处理全流程
state_machine_cleanup(sm); // 清理资源
return0;
}
将复杂的回调嵌套转换为线性的状态转移,显式管理内存分配与释放,避免回调地狱中因作用域不明确导致的内存泄漏。每个状态处理函数中检查关键操作(如内存分配)的返回值,异常时提前终止状态机,避免无效状态转移。
最后
好了,今天就跟大家分享这么多了,如果你觉得有所收获,一定记得点个赞~
唯一、永久、免费分享嵌入式技术知识平台~
推荐专辑 点击蓝色字体即可跳转
☞ MCU进阶专辑
☞ “bug说”专辑
☞ 专辑|手撕C语言
☞ 专辑|经验分享