首页 > 解决方案 > 将指向局部变量的指针保存(又名推送)到链表会导致 Segfault

问题描述

该程序将多个文件名作为命令行参数,并找出每个文件中的单词数以及每个单词在所有文件中出现的次数(即频率)。具体来说,程序将首先确定要处理的文件数。然后,程序将创建多个线程(每个文件一个)。每个线程都会计算给定文件的字数。此外,每个线程将访问一个全局链表并更新每个单词在所有文件中出现的次数。

但是,我无法在每个节点中打印单词。当我尝试时:

printf("%s appears %d times\n", node->word, node->count);

我遇到了分段错误。

Thread 1: number of words in File:input_file_1.txt is 6
Thread 2: number of words in File:input_file_2.txt is 14
All 2 files have been counted and the total of 20 words found !
Segmentation fault: 11

当我在链表中​​推送节点或打印链表时出现问题,但我无法弄清楚。这是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <pthread.h>

struct thread_info {
   pthread_t thread_id; /* ID returned by pthread_create() */
   int thread_num; /* Application-defined thread # */
   char * filename; /* From command-line argument filename */
};

// A linked list node
struct Node{
   char * word;
   int count;
   struct Node * next;
};

struct Node * head = NULL;
static int totalWordCount = 0;

void push(struct Node **head, char * new_data){
    struct Node * new_node = (struct Node * ) malloc(sizeof(struct Node));
    struct Node *last = *head;  

    new_node->word = new_data;
    new_node->count = 1;
    new_node->next = NULL; 

    if (*head == NULL) { 
       *head = new_node; 
       return; 
    }   

    while (last->next != NULL) 
        last = last->next; 

    last->next = new_node; 
    return;     
}

bool search(struct Node **head, char * x){
   struct Node * current = *head; // Initialize current
   while (current != NULL){
      if (strcmp(current, x) == 0){
         current->count++;
         return true;
      }
      current = current->next;
   }
   return false;
}


// This function prints contents of linked list starting from head

void printList(struct Node *head){
   struct Node *node = head;
   while (node != NULL){
        printf("%s appears %d times\n", node->word, node->count);
        node = node->next;
    }
}

void * processFile(void * vargp){
   int numberofwords = 0;
   // Store the value argument passed to this thread
   struct thread_info * tinfo = vargp;
   FILE * fp;
   fp = fopen(tinfo->filename, "r"); // read mode
   if (fp == NULL){
      perror("Error while opening the file.\n");
      exit(EXIT_FAILURE);
   }
   char word[100]; 
   while (fscanf(fp, "%s", word) != EOF) {
       if (search(&head,word)){
        } else{
            push(&head, word);
        }
       numberofwords+=1;
   }
   printf("Thread %d: number of words in File:%s is %d\n", tinfo->thread_num, tinfo->filename, numberofwords);
   totalWordCount += numberofwords;
   fclose(fp);
}

int main(int argc, char const * argv[]){
   pthread_t thread_id;
   char ch, file_name[25];
   int numberoffile = argc-1;
   for (size_t i = 0; i < numberoffile; i++){
      struct thread_info tinfo;
      tinfo.thread_num = i + 1;
      tinfo.filename = argv[i + 1];
      pthread_create( & tinfo.thread_id, NULL, processFile, & tinfo);
      pthread_join(tinfo.thread_id, NULL);
   }
   printf("All %d files have been counted and the total of %d words found !\n", argc - 1, totalWordCount);
   printList(head);
   //printf("%s appears %d times\n", head->word, head->count);
   return 0;
}

太感谢了!

标签: arrayscstringstructlinked-list

解决方案


您的代码中有几个问题。导致段错误的问题很可能是您将指向局部变量的指针保存(也称为推送)到链表。

processFile功能上:

   char word[100];   <---------------------- local variable
   while (fscanf(fp, "%s", word) != EOF) {
       if (search(&head,word)){
        } else{
            push(&head, word);  <----------- call push

push功能上:

void push(struct Node **head, char * new_data){
    struct Node * new_node = (struct Node * ) malloc(sizeof(struct Node));
    struct Node *last = *head;  

    new_node->word = new_data;  <------- save pointer

局部变量 likechar word[100];具有自动存储持续时间。这意味着该变量仅在您位于该函数内部(或从该函数调用的函数内部)时才存在。但是一旦processFile返回,该变量word会自动销毁,并且不再有效地访问内存。换句话说 - 您的列表包含指向无效内存的指针。

您需要做的是将单词保存在其他内存中。

为此,您至少有两个选择。

选项 1:更改节点定义并使用strcpy. 喜欢:

struct Node{
   char word[100];
   int count;
   struct Node * next;
};

在里面push做:

assert(strlen(new_data) < 100);
strcpy(new_node->word, new_data);

选项2:对链表中的单词使用动态分配。喜欢:

void push(struct Node **head, char * new_data){
    struct Node * new_node = malloc(sizeof(struct Node));
    struct Node *last = *head;  

    new_node->word = malloc(1 + strlen(new_data));
    assert(new_node != NULL);
    strcpy(new_node->word, new_data);

但是您的代码存在更多问题。例如:

strcmp(current, x) ---> strcmp(current->word, x)
       ^^^^^^^                 ^^^^^^^^^^^^^
    not a string               this is the string to compare

和这里:

  pthread_create( & tinfo.thread_id, NULL, processFile, & tinfo);
  pthread_join(tinfo.thread_id, NULL);

您创建一个线程,然后立即加入它。这实际上意味着您当时只有一个线程在运行!

代码应该有一个用于创建线程的循环和另一个用于加入线程的循环。

但是,如果您正确创建/加入,多线程实现将失败很长时间,因为所有线程将同时读取和修改链表。

您需要添加互斥锁保护,以便只有一个线程可以对列表进行操作。但是如果你这样做,线程将一直相互阻塞,因此拥有多个线程并没有真正的好处。

这真正意味着你的整体设计是有问题的。让多个线程使用同一个全局链表并不是一个好主意。

如果要执行此多线程,则每个线程在从文件中添加单词时都应该有自己的列表。然后,一旦文件读取完成,您将需要一些代码来合并列表。我不确定这是否值得,但你可以试一试。

最后,我不认为链表是这个应用程序的一个好的数据容器。搜索会很慢(即 O(N)),因此对于大文件(也就是很多单词)来说性能很差。考虑改用搜索树或一些基于哈希的表。


推荐阅读