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

CS50 Pset5 Speller程序Valgrind检测失败,存内存泄漏与指针错误

CS50x Pset5 Speller:Valgrind检测失败问题排查

正在完成CS50x课程的Pset5 Speller作业,代码功能正常,但check50的Valgrind检测不通过。用help50 Valgrind测试dictionaries/small字典时无内存泄漏,推测未调用fclose()关闭文件是原因之一,但添加该语句会触发munmap_chunk(): invalid pointer错误,附上代码和错误日志寻求问题定位。


代码

// Implements a dictionary's functionality

#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>

#include "dictionary.h"

// Represents a node in a hash table
typedef struct node
{
    char word[LENGTH + 1];
    struct node *next;
}
node;

// Number of bucket for Hash Table
const unsigned int N = 676;

// Hash table
node *table[N];

int word_counter = 0;

// Returns true if word is in dictionary, else false
bool check(const char *word)
{
    int index = hash(word);
    node *cursor = table[index];

    while (cursor != NULL)
    {
        if (strcasecmp(word, cursor->word) == 0)
        {
            return true;
        }
        cursor = cursor->next;
    }

    return false;
}

// Hashes word to a number
unsigned int hash(const char *word)
{
    unsigned int index = 0;
    for (int i = 0; i < 26; i++)
    {
        if (word[0] == i + 'a' || word[0] == i + 'A')
        {
            index += i * 26;
        }

        if (word[1] == i + 'a' || word[1] == i + 'A')
        {
            index += i;
        }
    }

    return index;
}

// Loads dictionary into memory, returning true if successful, else false
bool load(const char *dictionary)
{
    FILE *dict_file = fopen(dictionary, "r");
    if (dict_file == NULL)
    {
        return false;
    }

    table_clear();

    // Use fscanf() to look at each string in the dictionary and store to buffer
    char *buffer = malloc(sizeof(LENGTH + 1));
    if (buffer == NULL)
    {
        return false;
    }
    while (fscanf(dict_file, "%s", buffer) != EOF)
    {
        // Create a new memory space for each dictionary word, and store it here
        node *n = malloc(sizeof(node));
        if (n == NULL)
        {
            free(buffer);
            return false;
        }
        // Copy each word from buffer to the location (*dict_entry).word
        strcpy(n->word, buffer);
        n->next = NULL;

        // Use hash function to find the hash value of the dictionary word
        int index = hash(n->word);
        if (table[index] != NULL)
        {
            n->next = table[index];
        }
        table[index] = n;
        n = NULL;
        free(n);
        word_counter++;
    }
    //fclose(dict_file); <----- If activate, this line causes the munmap_chunk: invalid pointer error
    free(buffer);
    return true;
}

// Returns number of words in dictionary if loaded, else 0 if not yet loaded
unsigned int size(void)
{
    return word_counter;
}

// Unloads dictionary from memory, returning true if successful, else false
bool unload(void)
{
     for (int i = 0; i < N; i++)
     {
        node *cursor = table[i];
        while (cursor != NULL)
        {
            cursor = cursor->next;
            free(table[i]);
            table[i] = cursor;
        }

        free(cursor);
     }

    return true;
}

// Sets every value in table to NULL
void table_clear(void)
{
    for (int i = 0; i < N; i++)
    {
        table[i] = NULL;
    }
    return;
}

错误日志

**Cause valgrind tests failed; see log for more information

LOG:
running valgrind --show-leak-kinds=all --xml=yes --xml-file=/tmp/tmpr2rknwok -- ./speller substring/dict substring/text...
checking for output "MISSPELLED WORDS

ca
cats
caterpill
caterpillars

WORDS MISSPELLED: 4
WORDS IN DICTIONARY: 2
WORDS IN TEXT: 6
"...
checking that program exited with status 0...
checking for valgrind errors...
Invalid write of size 1: (file: dictionary.c, line: 88)
Invalid read of size 1: (file: dictionary.c, line: 99)
472 bytes in 1 blocks are still reachable in loss record 1 of 1: (file: dictionary.c, line: 74)

问题定位与修复

1. 缓冲区溢出(核心问题)

load函数中buffer的内存分配错误:

char *buffer = malloc(sizeof(LENGTH + 1));

sizeof(LENGTH +1)计算的是整数的字节数(通常为4或8),而非LENGTH+1个字符的空间,导致缓冲区过小,读取长单词时触发堆内存溢出,破坏内存结构。这也是添加fclose()后触发munmap_chunk()错误的根源。

修复:改为正确的内存分配方式:

char *buffer = malloc(LENGTH + 1);

2. 错误的节点内存释放

load函数中,将节点n加入哈希表后,执行了n = NULL; free(n);——这两行完全多余,且若后续逻辑出错会导致悬空指针。直接删除这两行即可。

3. 文件句柄未关闭

修复缓冲区溢出后,在load函数的free(buffer);之前添加fclose(dict_file);,即可正常关闭文件,解决Valgrind中“still reachable”的内存泄漏提示。

4. Unload函数的冗余操作

unload函数循环结束后执行free(cursor);,此时cursor已为NULL,无实际意义,直接删除该行。

5. 哈希函数的优化(可选)

原哈希函数会访问单词的第二个字符,若单词长度为1,虽不会崩溃,但可优化为仅处理存在的字符,避免无效判断:

unsigned int hash(const char *word)
{
    unsigned int index = 0;
    // 处理第一个字符
    if (isalpha(word[0]))
    {
        char c = tolower(word[0]);
        index += (c - 'a') * 26;
    }
    // 处理第二个字符(如果存在)
    if (isalpha(word[1]))
    {
        char c = tolower(word[1]);
        index += (c - 'a');
    }
    return index;
}

内容的提问来源于stack exchange,提问作者Zach Anderson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 22:01:01