You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

向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地址执行触发内存访问违规:

  1. 代码中定义的bar是接收函数指针、返回void* (*)(void*)类型函数指针的函数,但bar的实现里执行完outputFun(1,2)后直接返回NULL,没有返回有效的函数指针。
  2. main函数中调用bar(...)给foo赋值后,foo的值就是NULL,pthread_create拿到NULL作为线程入口,启动新线程时自然会跳转到0地址,和valgrind输出的Jump to the invalid address 0x0完全对应。
  3. 预期实现的“部分应用(预绑定函数参数)”逻辑无法通过这种返回函数指针的方式在标准C里实现:标准C不支持运行时动态生成函数,gcc的嵌套函数扩展属于非标准特性,也不能在定义它的函数作用域外返回使用,会因为栈内存失效触发未定义行为。
修复方案

C语言实现线程逻辑预绑定参数的标准做法是自定义参数结构体,把需要预传入的内容(包括要调用的目标函数、其他自定义参数)打包,通过pthread_create的第四个void*类型参数传入线程入口:

  1. 定义结构体存储所有需要预绑定的参数
  2. 写签名严格符合void* (*)(void*)要求的统一线程入口函数,在入口内解析参数、执行对应逻辑
  3. 创建线程前初始化参数结构体,把结构体指针作为入参传给线程

修正后的可运行代码如下:

#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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 21:36:22