C 为什么Valgrind在这个哈希表测试用例中查找错误?

C 为什么Valgrind在这个哈希表测试用例中查找错误?,c,memory,pthreads,valgrind,C,Memory,Pthreads,Valgrind,我自己开发了一个C中的并发通用哈希表 哈希表.h的相关内容: typedef struct list_node { void * data; struct list_node * next; } list_node_t; typedef struct hash_table { int max_size; int count; list_node_t * * elements; pthread_rwlock_t * locks; pthr

我自己开发了一个C中的并发通用哈希表

哈希表.h的相关内容

typedef struct list_node {
    void * data;
    struct list_node * next;
} list_node_t;


typedef struct hash_table {
    int max_size;
    int count;
    list_node_t * * elements;
    pthread_rwlock_t * locks;
    pthread_rwlock_t global_table_lock;
    hash_table_compare_function compare;
    hash_table_hash_function hash;
} hash_table_t;
hash_table.c
的相关内容:

#define LOCK_RD(lock)   pthread_rwlock_rdlock(&lock);
#define LOCK_WR(lock)   pthread_rwlock_wrlock(&lock);
#define UNLOCK(lock)    pthread_rwlock_unlock(&lock);

bool
hash_table_remove(hash_table_t * table, void * element)
{
    int hash_value = table->hash(element);
    list_node_t * node, * prev;

    LOCK_WR(table->locks[hash_value]);

    node = table->elements[hash_value];
    prev = NULL;

    while (node) {
        if (!table->compare(node->data, element)) {
            // value is first item in the list
            if (node == table->elements[hash_value]) {
                table->elements[hash_value] = node->next;
                free(node);
                UNLOCK(table->locks[hash_value]);
                LOCK_WR(table->global_table_lock);
                table->count--;
                UNLOCK(table->global_table_lock);
                return true;
            } else {
                // link previous node with one after current
                prev->next = node->next;
                free(node);
                UNLOCK(table->locks[hash_value]);
                LOCK_WR(table->global_table_lock);
                table->count--;
                UNLOCK(table->global_table_lock);
                return true;
            }
        }
        prev = node;
        node = node->next;
    }

    UNLOCK(table->locks[hash_value]);

    return false;
}
我编写了一个使用字符串的测试用例,其中的相关代码如下:

#include "hashtable.h"

#define NUM_THREADS 2
#define NUM_STRINGS 154560
#define NUM_LOOKUPS 10000


void *
do_work(void * data)
{
    int thread_id = *(int*)data;

    // write "threadX.txt" to filename, where X is the given thread id
    char filename[64];
    strcpy(filename, "thread");
    char thread_id_str[4];
    sprintf(thread_id_str, "%d", thread_id);
    strcat(filename, thread_id_str);
    strcat(filename, ".txt");

    FILE * file = fopen(filename, "r");
    char buffer[128];
    int i, num_str_per_thread = NUM_STRINGS / NUM_THREADS;
    char * str_array[num_str_per_thread];

    for (i = 0; i < num_str_per_thread; i++) {
        fgets(buffer, 128, file);

        str_array[i] = calloc((strlen(buffer) + 1), sizeof(char));
        strcpy(str_array[i], buffer);
    }

    fclose(file);

    for (i = 0; i < num_str_per_thread; i++)
        hash_table_insert(table, str_array[i]);

    for (i = 0; i < NUM_LOOKUPS; i++)
        hash_table_contains(table, str_array[rand() % num_str_per_thread]);

    for (i = 0; i < num_str_per_thread / 2; i++)
        hash_table_remove(table, str_array[rand() % num_str_per_thread]);

    //sleep(2); NOTE: no read errors reported if I leave this sleep() here.

    for (i = 0; i < num_str_per_thread; i++)
        if (str_array[i])
            free(str_array[i]);

    return NULL;
}


void
create_workers()
{
    pthread_t threads[NUM_THREADS];
    int ids[NUM_THREADS];
    int i;

    for (i = 0; i < NUM_THREADS; i++)
        ids[i] = i + 1;

    for (i = 0; i < NUM_THREADS; i++)
        pthread_create(&threads[i], NULL, do_work, (void*)&ids[i]);

    for (i = 0; i < NUM_THREADS; i++)
        pthread_join(threads[i], NULL);
}
#包括“hashtable.h”
#定义NUM_线程2
#定义NUM_字符串154560
#定义NUM_查找10000
空虚*
工作(无效*数据)
{
int-thread_-id=*(int*)数据;
//将“threadX.txt”写入文件名,其中X是给定的线程id
字符文件名[64];
strcpy(文件名,“线程”);
char-thread_-id_-str[4];
sprintf(线程id线程,“%d”,线程id);
strcat(文件名,线程id\u str);
strcat(文件名为“.txt”);
FILE*FILE=fopen(文件名,“r”);
字符缓冲区[128];
int i,num_str_per_thread=num_STRINGS/num_THREADS;
char*str_数组[num_str_per_thread];
对于(i=0;i
测试用例的工作原理如下:有两个文件,thread1.txt和thread2.txt,每个文件都包含我事先生成的唯一的字符串。我创建了两个线程,每个线程将从一个文件中读取并将每个字符串存储在一个名为
str\u array
的字符串数组中。然后,他们将所有这些字符串插入哈希表,并执行随机搜索(
hash\u table\u contains
)和删除(
hash\u table\u remove
)。然后,每个将释放各自的字符串数组。但是,当我运行此测试用例时,Valgrind报告如下:

请注意,没有内存泄漏。我从这些错误中得到的是,一个线程在调用
hash\u table\u remove
时,试图释放
free(str\u array[I])
已经释放的内存。然而,这毫无意义,因为
hash\u table\u remove
是在
free(str\u array[i]
之前调用的。我不知道是什么原因导致了这些无效读取


提前感谢您!

在这里,您的线程最多删除插入的字符串的一半:

for (i = 0; i < num_str_per_thread / 2; i++)
    hash_table_remove(table, str_array[rand() % num_str_per_thread]);
for (i = 0; i < num_str_per_thread; i++)
    if (str_array[i])
        free(str_array[i]);
for(i=0;i
(事实上,它最有可能删除插入的字符串的39%)

然后,它继续释放它插入的所有字符串:

for (i = 0; i < num_str_per_thread / 2; i++)
    hash_table_remove(table, str_array[rand() % num_str_per_thread]);
for (i = 0; i < num_str_per_thread; i++)
    if (str_array[i])
        free(str_array[i]);
for(i=0;i
但是,这些字符串中至少有一半(很可能是~61%)仍在哈希表中,其他线程在扫描链式哈希桶条目时将尝试在哈希表中对它们进行比较。这是无错误后的用法

您可以在删除字符串时释放它们,而不是释放所有字符串:

for (i = 0; i < num_str_per_thread / 2; i++)
{
    int str_index = rand() % num_str_per_thread;

    if (str_array[str_index])
    {
        hash_table_remove(table, str_array[str_index]);
        free(str_array[str_index]);
        str_array[str_index] = NULL;
    }
}
for(i=0;i
此时,
str_array[]
中的非空项是哈希表中仍然存在的字符串。只有将它们从哈希表中删除(或哈希表不再使用),才能释放它们


<> P>你的测试用例出错的事实是一个很好的指示器,你的接口的人机工程学不如它的好。你应该考虑一个设计,其中插入的字符串的所有权被转移到哈希表中,以便<代码> HasyTabelyReleVIEW()
本身负责释放字符串。

您是否认为从文件中读取的数据被“\0”终止?这可能会损坏字符串缓冲区。
strcpy
处理
\0
终止,如果我没有弄错的话。谢谢您的回复!如果没有MCVE()(或MRE或现在使用的任何名称;MCVE已经使用了五年多,不需要更改)或SSCCE(),以允许我们进行实验。在有一个(最小)的最小示例之前,我不会费心尝试它复制问题的数据。@如果源未终止,saucygote strcpy无法插入\0。它将继续复制,直到到达一个\0,该值可能超过字符串缓冲区的结尾。但是strlen也会有同样的问题。@jo art:如果
fgets()
成功,它将始终返回一个字符串(这意味着它以null结尾)-源文件不需要有一个。但是,问题中的代码不检查
fgets()
成功…对延迟的响应表示歉意。关于您回答的最后一条建议,我认为让数据结构不处理释放插入其中的对象内存的问题是一种很好的做法。我想您可以边走边学。非常感谢!@SaucyGoat:通常您会将键和对象分开