C语言函数指针和回调函数怎么用?原理与常见坑一次讲清
C语言里的函数指针,是不少人学了多年 C 仍然含糊的概念。倒不是因为它多复杂——本质上就是"把函数的入口地址存到变量里"——但声明语法看着劝退,项目里该用的时候又想不起来。回调函数更甚:知道 qsort 要传比较函数,但让自己设计一个事件系统,就不知道从哪下手了。
这篇文章从函数指针的声明和调用讲起,再到回调机制的原理和工程实践,最后说清楚容易踩的坑。
函数指针:存的是函数入口地址
函数编译后加载到内存,函数名就是入口地址。把这个地址赋给一个变量,这个变量就是函数指针。
声明方式看着别扭,但拆开看规律很清晰:
cint (*fp)(int, int); // 指向「两个int参数、返回int」的函数
核心语法:返回类型 (*指针名)(参数列表)。(*指针名) 外面的括号不能省——省了就变成声明一个返回 int* 的函数,即指针函数。这两者经常被搞混:
| 函数指针 | 指针函数 | |
|---|---|---|
| 本质 | 指向函数的指针 | 返回指针的函数 |
| 声明 | int (*p)(int) | int* f(int) |
* 归属 | 跟指针变量名结合 | 跟返回类型结合 |
用 typedef 简化声明
实际项目里函数指针的声明几乎都用 typedef 包一层,否则可读性极差:
ctypedef int (*CompareFunc)(const void*, const void*); CompareFunc cmp = my_compare; // 之后直接当类型名用
C 标准库 qsort 的第四个参数,不用 typedef 的话长这样:int (*)(const void*, const void*)——每次手写都是折磨。
函数指针数组
多个同类型函数指针放进数组,用下标切换——这是状态机和命令分发的基础写法:
cvoid state_idle(void) { /* 空闲状态处理 */ } void state_running(void) { /* 运行状态处理 */ } void (*states[])(void) = { state_idle, state_running }; states[current_state](); // O(1) 跳转,比 switch-case 更干净
新增状态只需加一个函数和数组元素,不用改动分发逻辑。嵌入式开发和网络协议解析里特别常见。
回调函数:把函数当参数传给别人
回调的本质:你定义一个函数,但不自己调用,而是把函数指针传给另一个函数,让对方在合适的时候反过来调用你。
最经典的例子:qsort
cint 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* 参数:
ctypedef 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。搞反了排序结果全错但不报错,排查半天找不到原因。
多线程竞态:一个线程注册回调,另一个线程触发回调,没有同步保护。轻则数据错乱,重则崩溃。回调的注册和触发必须加锁或用原子操作。