向pthread传入带函数指针参数的函数指针时触发Segfault
问题背景
- 运行环境:x86-64架构Ubuntu,gcc编译器
- 现象:程序无编译报错,运行时触发段错误
- 实现目标:创建pthread线程时,根据条件选择传入不同的目标函数,通过部分应用修改线程实际执行逻辑
- 当前状态:未定位到问题根因,无法解决段错误
复现代码
#include <stdio.h> #include <pthread.h> #include <time.h> #include <stdlib.h> #include <stdint.h> void * (*foo)(void*); void * (*bar(void (*outputFun)(uint8_t a, short unsigned int b)))(void *); void baz(u_int8_t a, short unsigned int b) { printf("a - b is %d - %d\n", a, b); } void bay(u_int8_t a, short unsigned int b) { printf("b - a is %d - %d\n", b, a); } int main() { srand(time(NULL)); unsigned int random = 1 + rand() % 2; printf("Our random number is: %d\n", random); foo = bar(random > 1 ? *baz : *bay); pthread_t thread; pthread_create(&thread, NULL, foo , NULL); // DO SOME STUFF pthread_join(thread, NULL); } void * (*bar(void (*outputFun)(uint8_t a, short unsigned int b)))(void *) { // DO OTHER STUFF outputFun(1, 2); return NULL; }
valgrind错误输出
Our random number is: 2 a - b is 1 - 2 --15699-- REDIR: 0x4914b10 (libc.so.6:calloc) redirected to 0x483dce0 (calloc) ==15699== Thread 2: ==15699== Jump to the invalid address stated on the next line ==15699== at 0x0: ??? ==15699== by 0x485E608: start_thread (pthread_create.c:477) ==15699== by 0x4998132: clone (clone.S:95) ==15699== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==15699== ==15699== ==15699== Process terminating with default action of signal 11 (SIGSEGV) ==15699== Bad permissions for mapped region at address 0x0 ==15699== at 0x0: ??? ==15699== by 0x485E608: start_thread (pthread_create.c:477) ==15699== by 0x4998132: clone (clone.S:95) --15699-- REDIR: 0x49136d0 (libc.so.6:free) redirected to 0x483c9d0 (free) ==15699== ==15699== HEAP SUMMARY: ==15699== in use at exit: 272 bytes in 1 blocks ==15699== total heap usage: 2 allocs, 1 frees, 1,296 bytes allocated ==15699== ==15699== Searching for pointers to 1 not-freed blocks ==15699== Checked 8,476,712 bytes ==15699== ==15699== LEAK SUMMARY: ==15699== definitely lost: 0 bytes in 0 blocks ==15699== indirectly lost: 0 bytes in 0 blocks ==15699== possibly lost: 272 bytes in 1 blocks ==15699== still reachable: 0 bytes in 0 blocks ==15699== suppressed: 0 bytes in 0 blocks ==15699== Rerun with --leak-check=full to see details of leaked memory ==15699== ==15699== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) ==15699== ==15699== 1 errors in context 1 of 1: ==15699== Jump to the invalid address stated on the next line ==15699== at 0x0: ??? ==15699== by 0x485E608: start_thread (pthread_create.c:477) ==15699== by 0x4998132: clone (clone.S:95) ==15699== Address 0x0 is not stack'd, malloc'd or (recently) free'd ==15699== ==15699== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) Segmentation fault (core dumped)
根因分析
段错误的直接原因是传给pthread_create的线程入口函数指针是空指针NULL,线程启动后跳转到0地址执行触发内存访问违规:
- 代码中定义的
bar是接收函数指针、返回void* (*)(void*)类型函数指针的函数,但bar的实现里执行完outputFun(1,2)后直接返回NULL,没有返回有效的函数指针。 - main函数中调用
bar(...)给foo赋值后,foo的值就是NULL,pthread_create拿到NULL作为线程入口,启动新线程时自然会跳转到0地址,和valgrind输出的Jump to the invalid address 0x0完全对应。 - 预期实现的“部分应用(预绑定函数参数)”逻辑无法通过这种返回函数指针的方式在标准C里实现:标准C不支持运行时动态生成函数,gcc的嵌套函数扩展属于非标准特性,也不能在定义它的函数作用域外返回使用,会因为栈内存失效触发未定义行为。
修复方案
C语言实现线程逻辑预绑定参数的标准做法是自定义参数结构体,把需要预传入的内容(包括要调用的目标函数、其他自定义参数)打包,通过pthread_create的第四个void*类型参数传入线程入口:
- 定义结构体存储所有需要预绑定的参数
- 写签名严格符合
void* (*)(void*)要求的统一线程入口函数,在入口内解析参数、执行对应逻辑 - 创建线程前初始化参数结构体,把结构体指针作为入参传给线程
修正后的可运行代码如下:
#include <stdio.h> #include <pthread.h> #include <time.h> #include <stdlib.h> #include <stdint.h> // 打包线程需要的所有预绑定参数 typedef struct { void (*outputFun)(uint8_t a, unsigned short b); // 可按需添加其他需要传入线程的参数 } ThreadArgs; void baz(uint8_t a, unsigned short b) { printf("a - b is %d - %d\n", a, b); } void bay(uint8_t a, unsigned short b) { printf("b - a is %d - %d\n", b, a); } // 线程入口,签名严格匹配pthread要求 void* thread_entry(void* arg) { ThreadArgs* args = (ThreadArgs*)arg; args->outputFun(1, 2); // 可添加其他线程逻辑 return NULL; } int main() { srand(time(NULL)); unsigned int random = 1 + rand() % 2; printf("Our random number is: %d\n", random); pthread_t thread; ThreadArgs args; // 根据条件选择要执行的目标函数 args.outputFun = random > 1 ? baz : bay; // 传入参数结构体指针 pthread_create(&thread, NULL, thread_entry, &args); pthread_join(thread, NULL); return 0; }
编译时添加-lpthread链接线程库即可正常运行。如果线程参数的生命周期可能短于线程执行周期,可以用malloc给参数结构体分配堆内存,在线程执行结束前调用free释放,避免悬垂指针问题。
内容的提问来源于stack exchange,提问作者peads
相关产品推荐
相关产品推荐

