linux c语言多线程编程怎么传递参数?创建线程后立刻返回,出现段错误(Segmentation fault)的原因和解决办法

linux 下 c 语言编程,使用多线程是方便的。有时候,我们需要函数创建线程后立刻返回,线程继续在后台运行。可是,却很容易出现段错误(Segmentation fault),请看下面这段代码:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

typedef struct __PARAM
{
    char*           str;
    int             cnt;
}PARAM;

void* thread(void* pdata)
{
    PARAM* p = (PARAM*)pdata;

    printf("str's ptr in %s: %p - %d\n", __FUNCTION__, p->str, p->cnt);
    sleep(1);
    printf("\n    in thread:\n");
    printf("str(%p): %s", p->str, p->str);
    printf("cnt: %d\n\n", p->cnt);

    return NULL;
}

int ifun(char* str, int cnt)
{
    pthread_t pid;
    PARAM param = {0};

    printf("str's ptr in %s: %p\n", __FUNCTION__, str);

    param.str = str;
    param.cnt = cnt;

    if(0>pthread_create(&pid, NULL, thread, &param)){
       printf("%s create thread failed\n", __FUNCTION__);
       return -1;
    }

    return 0;
}

int main()
{
    char str[] = "hello world\n";
    int  cnt = 88;

    printf("str's ptr in %s: %p\n", __FUNCTION__, str);
    ifun(str, cnt);

    sleep(3);

    printf("\nfinished\n\n");
    return 0;
}

我们编译执行,发现出现了段错误:

$ gcc thread_resource.c -lpthread -g
$ ./a.out 
str's ptr in main: 0x7ffec58dbcd0
str's ptr in ifun: 0x7ffec58dbcd0
str's ptr in thread: 0x7ffec58dbcd0 - 88

    in thread:
Segmentation fault (core dumped)

这是为什么呢?

创建线程后立刻返回,出现段错误的原因

linux 下程序每调用一个函数,就会为该函数创建一个栈帧,被调用函数的局部变量等信息都会保存在栈帧里。被调用函数返回后,它的栈帧就会被销毁了。如果之前传递了栈帧里的参数给线程函数,此时线程函数再引用这里的参数,操作系统就会判定引用非法,就会出现Segmentation fault(段错误)。

我们来看看上述代码的对应的汇编代码:

$ objdump -dS a.out 

a.out:     file format elf64-x86-64
...
int ifun(char* str, int cnt)
{
  400820:   55                      push   %rbp
  400821:   48 89 e5                mov    %rsp,%rbp
  400824:   48 83 ec 40             sub    $0x40,%rsp
  400828:   48 89 7d c8             mov    %rdi,-0x38(%rbp)
  40082c:   89 75 c4                mov    %esi,-0x3c(%rbp)
  40082f:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  400836:   00 00 
  400838:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  40083c:   31 c0                   xor    %eax,%eax
    pthread_t pid;
    PARAM param = {0};
  40083e:   48 c7 45 e0 00 00 00    movq   $0x0,-0x20(%rbp)     # 传递给线程的 param 在 ifun 的栈帧里,栈帧销毁后无法再使用
  400845:   00 
  400846:   48 c7 45 e8 00 00 00    movq   $0x0,-0x18(%rbp)
  40084d:   00 
...

解决办法

问题的关键就是线程函数引用的参数被销毁了,那么只要不让其销毁,就不会出现段错误了。方法有两个,一是创建全局变量作为传递给线程函数的参数,全局变量不会随着函数的返回被销毁。一是动态分配一个变量作为线程函数的参数,动态分配的变量存在堆中,也不会在函数返回时被销毁。因为全局变量对程序的独立封装性有破坏,所以下面的例子使用了动态分配变量来解决问题:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

typedef struct __PARAM
{
    char*           str;
    int             cnt;
}PARAM;

void* thread(void* pdata)
{
    PARAM* p = (PARAM*)pdata;

    printf("str's ptr in %s: %p - %d\n", __FUNCTION__, p->str, p->cnt);
    sleep(1);
    printf("\n    in thread:\n");
    printf("str(%p): %s", p->str, p->str);
    printf("cnt: %d\n\n", p->cnt);

    free(p);

    return NULL;
}

int ifun(char* str, int cnt)
{
    PARAM* param = (PARAM*)malloc(sizeof(PARAM));
    pthread_t pid;

    printf("str's ptr in %s: %p\n", __FUNCTION__, str);

    param->str = str;
    param->cnt = cnt;

    if(0>pthread_create(&pid, NULL, thread, param)){
       printf("%s create thread failed\n", __FUNCTION__);
       return;
    }

    return 0;
}

int main()
{
    char str[] = "hello world\n";
    int  cnt = 88;

    printf("str's ptr in %s: %p\n", __FUNCTION__, str);

    ifun(str, cnt);

    sleep(3);

    printf("\nfinished\n\n");
    return 0;
}

编译执行结果如下:

$ gcc thread_resource.c -lpthread -g
$ ./a.out 
str's ptr in main: 0x7fffe5e35fc0
str's ptr in ifun: 0x7fffe5e35fc0
str's ptr in thread: 0x7fffe5e35fc0 - 88

    in thread:
str(0x7fffe5e35fc0): hello world
cnt: 88


finished

成功了,程序没有再出现段错误。这样,我们就解决了函数创建线程后立刻返回,线程出现段错误(Segmentation fault)的问题。

阅读更多:   C语言 , Linux笔记
添加新评论

icon_redface.gificon_idea.gificon_cool.gif2016kuk.gificon_mrgreen.gif2016shuai.gif2016tp.gif2016db.gif2016ch.gificon_razz.gif2016zj.gificon_sad.gificon_cry.gif2016zhh.gificon_question.gif2016jk.gif2016bs.gificon_lol.gif2016qiao.gificon_surprised.gif2016fendou.gif2016ll.gif