6月4日 13:36

C语言函数指针和回调函数怎么用?原理与常见坑一次讲清

C语言里的函数指针,是不少人学了多年 C 仍然含糊的概念。倒不是因为它多复杂——本质上就是"把函数的入口地址存到变量里"——但声明语法看着劝退,项目里该用的时候又想不起来。回调函数更甚:知道 qsort 要传比较函数,但让自己设计一个事件系统,就不知道从哪下手了。

这篇文章从函数指针的声明和调用讲起,再到回调机制的原理和工程实践,最后说清楚容易踩的坑。

函数指针:存的是函数入口地址

函数编译后加载到内存,函数名就是入口地址。把这个地址赋给一个变量,这个变量就是函数指针。

声明方式看着别扭,但拆开看规律很清晰:

c
int (*fp)(int, int); // 指向「两个int参数、返回int」的函数

核心语法:返回类型 (*指针名)(参数列表)(*指针名) 外面的括号不能省——省了就变成声明一个返回 int* 的函数,即指针函数。这两者经常被搞混:

函数指针指针函数
本质指向函数的指针返回指针的函数
声明int (*p)(int)int* f(int)
* 归属跟指针变量名结合跟返回类型结合

用 typedef 简化声明

实际项目里函数指针的声明几乎都用 typedef 包一层,否则可读性极差:

c
typedef int (*CompareFunc)(const void*, const void*); CompareFunc cmp = my_compare; // 之后直接当类型名用

C 标准库 qsort 的第四个参数,不用 typedef 的话长这样:int (*)(const void*, const void*)——每次手写都是折磨。

函数指针数组

多个同类型函数指针放进数组,用下标切换——这是状态机和命令分发的基础写法:

c
void state_idle(void) { /* 空闲状态处理 */ } void state_running(void) { /* 运行状态处理 */ } void (*states[])(void) = { state_idle, state_running }; states[current_state](); // O(1) 跳转,比 switch-case 更干净

新增状态只需加一个函数和数组元素,不用改动分发逻辑。嵌入式开发和网络协议解析里特别常见。

回调函数:把函数当参数传给别人

回调的本质:你定义一个函数,但不自己调用,而是把函数指针传给另一个函数,让对方在合适的时候反过来调用你。

最经典的例子:qsort

c
int compare_asc(const void* a, const void* b) { return *(int*)a - *(int*)b; } int arr[] = {5, 2, 8, 1, 9}; qsort(arr, 5, sizeof(int), compare_asc);

qsort 不关心升序还是降序,它只通过你传的比较函数来决定顺序。想降序?把 a - b 换成 b - a 就行。

回调怎么传递上下文数据

C 语言没有闭包,回调函数拿不到外部变量。标准做法是多传一个 void* 参数:

c
typedef void (*Callback)(int result, void* ctx); void async_read(Callback cb, void* ctx) { int r = do_read(); cb(r, ctx); // 原样把上下文传回去 }

调用方把结构体指针转成 void* 传进去,回调里再转回来。GLib、libevent、libuv 都采用这个模式。qsort 没有设计这个参数是个遗憾,实际项目里只好用全局变量绕过,既不优雅也不线程安全。

事件驱动模型

回调是事件驱动的基础设施:GUI 框架注册按钮点击回调,网络库注册连接/断开回调,操作系统注册信号处理回调——本质上都是"你告诉我事件发生时该调谁"。libuv 的事件循环就是典型的回调驱动架构。

容易踩的坑

类型不匹配:函数指针类型必须严格匹配返回值和参数列表。强制类型转换后调用,栈帧错乱,调试极难定位——有时能跑有时崩,症状不稳定。

空指针调用:回调没注册就被触发,函数指针是 NULL。调用前必须检查 if (fp != NULL)

过期指针dlopen 加载动态库拿到函数指针,dlclose 之后还调用——段错误。JIT 编译的代码被回收后继续调用也一样。

qsort 比较函数语义搞反:返回值是正/零/负,不是 true/false。搞反了排序结果全错但不报错,排查半天找不到原因。

多线程竞态:一个线程注册回调,另一个线程触发回调,没有同步保护。轻则数据错乱,重则崩溃。回调的注册和触发必须加锁或用原子操作。

标签:C语言