Linux定时器回调中void*指针转换后args地址不一致问题
问题成因
你观察到的"地址不一致"本质是两个问题叠加导致的,其中一个是低级的打印逻辑错误,另一个是会直接导致程序崩溃的内存使用bug:
- 打印对比的根本不是同一个对象:在
timer_start函数中,你打印的cbargs.args是cb_wrapper结构体里args成员存储的指针值(也就是你传入的用户参数地址);但在回调包装函数callback_wrapper中,你打印的是&cb->args,也就是cb指向的cb_wrapper结构体内部,args这个成员本身所在的内存地址。这两个值从定义上就不可能相等,和通用指针类型转换没有任何关系。如果要验证参数是否正确传递,你应该在回调中直接打印cb->args的值,而不是对成员取地址。 - 存在栈内存悬空的致命隐患:你赋值给
evp.sigev_value.sival_ptr的是局部变量cbargs的栈地址,timer_start函数执行完返回后,这个栈帧就会被回收,cbargs占用的栈内存会被后续函数调用覆写。你现在运行时看到sival_ptr地址和回调里拿到的cb地址一致,只是因为定时器触发间隔短,对应栈内存还没被改写,一旦程序运行逻辑变复杂、定时器触发时机变晚,这里必然出现野指针,导致回调函数跑飞、段错误。
解决方法
按照以下步骤修正代码即可:
- 修正日志打印逻辑:校验参数传递正确性时,在回调中直接打印
cb->args的值,不要使用取地址符&取结构体成员的地址做对比。 - 将回调上下文从栈内存改为堆内存分配,避免悬空指针:
- 把
timer_start中栈上定义的struct cb_wrapper cbargs;改为堆分配:struct cb_wrapper *cbargs = calloc(1, sizeof(struct cb_wrapper)); cbargs->callback = callback; cbargs->args = args; evp.sigev_value.sival_ptr = cbargs; - 在回调函数执行完用户注册的回调后,释放这块堆内存,避免内存泄漏:
void callback_wrapper(union sigval sv) { struct cb_wrapper *cb = (struct cb_wrapper*)(sv.sival_ptr); cb->callback(cb->args); free(cb); // 释放堆上分配的上下文内存 }
- 把
- 额外修复你代码里的另一个内存分配bug:当前代码中
*timer_handle = (timer_handle_t) calloc(1, sizeof(timer_handle_t));的写法是错的,如果timer_handle_t是指针类型,sizeof(timer_handle_t)得到的是指针本身的长度(通常是8字节),不是其指向的定时器句柄结构体的长度,会造成内存越界。分配时应该传入句柄结构体的实际大小,例如如果定义是typedef struct timer_handle_s *timer_handle_t;,分配代码要改为*timer_handle = (timer_handle_t)calloc(1, sizeof(struct timer_handle_s));。
内容的提问来源于stack exchange,提问作者dhon2407
相关产品推荐
相关产品推荐

