如何更改我的代码(哈希表+链表)以避免内存泄漏?

How to change my code (hashtable + linked lists) to avoid memory leaks?

抱歉,如果以下问题看起来很幼稚,我是编程的新手,仍然在基本概念上挣扎。

我编写了一段代码,将字典(txt 文件)加载到 hashtable/linked 列表数据结构中,程序编译并提供预期结果,但 Valgrind 显示内存泄漏。所有主要功能(节点的创建、人口、搜索、打印)工作正常,除了 destroy 功能(一个用于从 mallocs 释放内存)。 Valgrind 显示内存泄漏。

我在这里错过了什么?如何使我的代码更简洁?

感谢您的宝贵时间!你真棒!

我知道至少部分原因是我填充数据结构的方法 - 我使用 2 个 char* 缓冲区(tmp 和 tmp1,在循环内部和外部)来处理来自 fscanf 的输入到节点中。我想在卸载期间我可以在外部缓冲区 ("tmp") 上使用 'free' 函数,但不能在内部缓冲区 ("tmp1") 上使用。

我尝试使用 1 个缓冲区(循环内部和外部)和 2 个静态初始化的缓冲区 (char tmp[])。这两种方法都只用 1 个单词(字典中的最后一个)填充结构。

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

//定义节点结构

typedef struct node
{
    char* word;
    struct node* next;
}node;

// 哈希大小变量和哈希函数(感谢 K&R)

const int hash_elements = 100;

unsigned hash(char *s)
{
    unsigned hashval;
    for (hashval = 0; *s != '[=12=]'; s++)
    hashval = *s + 31 * hashval;
    return hashval % hash_elements;
}

// 将节点添加到 ll

开头的函数
int addnode_start (node** head, char* word)
{
    node* new_node = malloc(sizeof(node));
    if (new_node == NULL)
    {
        return 1;
    }
    new_node->word = word;
    new_node->next = *head;
    *head = new_node;
    return 0;
}

// 释放分配内存的函数

void destroy (node* hashtable)
{
    if (hashtable == NULL)
    {
        return;
    }
    else
    {
        destroy(hashtable->next);
        free(hashtable);
    }
}

// 主要功能 - 将 txt 文件中的字符串放入 hashtable/linked 列表数据结构

int main (int argc, char* argv[])
{
    if (argc < 2)
    {
        printf("Usage: ./stringdll dictionary.txt\n");
        return 1;
    }

// 为链表(哈希表)创建一个数组并为头部分配内存

node* hashtable[hash_elements];
for (int i = 0; i < hash_elements; i++)
{
    hashtable[i] = malloc(sizeof(node));
    if (hashtable[i] == NULL)
    {
        return 1;
    }
}

// 打开词典文件进行阅读

char* infile = argv[1];
FILE* input = fopen(infile, "r");
if (input == NULL)
{
    printf("File does not exist.\n");
    return 1;
}

// 为字符串定义临时存储

char* tmp = malloc(41);

// 扫描文件中的字符串并填充链表

while(fscanf(input, "%s", tmp) != EOF)
{
    char* tmp1 = malloc(41);
    for (int h = 0; h < hash_elements; h++)
    {
        if (hash(tmp) == h)
        {
            if (hashtable[h]->word == '[=19=]')
            {
                hashtable[h]->word = strcpy(tmp1, tmp);
                hashtable[h]->next = NULL;
            }
            else
            {
                int tmp_0 = addnode_start(&hashtable[h], strcpy(tmp1, tmp));
                if (tmp_0 == 1)
                {
                    return 1;
                }
            }
        }
    }
}

// 卸载字典

for (int d = 0; d < hash_elements; d++)
{
    destroy (hashtable[d]);
}

return 0;

}

我希望 Valgrind 能够证明在我的程序运行期间没有内存泄漏。

问题包括

  1. 您对散列 table 的原始初始化有问题:

    for (int i = 0; i < hash_elements; i++)
    {
        hashtable[i] = malloc(sizeof(node));
        if (hashtable[i] == NULL)
        {
            return 1;
        }
    }
    

    您正在为节点分配 space,但您没有使用有效数据填充节点。这种分配似乎完全没有用。看起来将 NULL 分配给 hashtable 元素不仅更容易而且更正确:

    for (int i = 0; i < hash_elements; i++) {
        hashtable[i] = NULL;
    }
    

    根据我对您的其他代码的了解,我认为它甚至可以在不进行其他更改的情况下处理该问题。

  2. 不清楚为什么需要为 tmp 动态分配 space。您似乎没有对它做任何需要动态分配的事情——没有运行时确定的长度,没有超出其声明范围的使用,……——所以将它声明为普通的可能会更清晰、更容易数组:

    char tmp[41];
    

    您可以按照显示的所有相同方式使用该版本的 tmp,并且无需释放它。

  3. 您为输入文本的副本动态分配 space,但从未释放它。指向那些 space 的指针最初记录在 tmp1 中,然后分配给您节点的 word 成员。您不需要或不想释放 tmp1 中保存的任何值,因此,因为您仍在使用这些指针,但是您的 destroy() 函数可以而且应该在释放每个节点的 word 之前释放它节点.