简体   繁体   中英

How can I understand a memory leak from valgrind output?

I'm taking the CS50 class and I'm on Week 5 and trying to figure out Pset5 Speller. For anyone who isn't familiar, the goal is to edit a particular .c file to get five functions to run properly so that the main function (located in a separate file) can do the following:

  1. LOAD - load a dictionary into a hash table
  2. HASH - run a word through this function to help load it into the dictionary or search for the word later
  3. SIZE - see how many words are in the dictionary
  4. CHECK - to see if a word from a text is in the dictionary
  5. UNLOAD - release the dictionary so there are no memory leaks

Please note that the file was given to me in the class and I am to edit the space within the functions - the only other thing I can change is const unsigned int N = 1000; which I set to 1000 just as an arbitrary number, but it could be anything.

I am having trouble with only one thing (that I can tell). I have done everything to make it run, but Check50 (the program to tell if I did it correctly) is telling me that I have memory errors:

Results for cs50/problems/2021/x/speller generated by check50 v3.3.0
:) dictionary.c exists
:) speller compiles
:) handles most basic words properly
:) handles min length (1-char) words
:) handles max length (45-char) words
:) handles words with apostrophes properly
:) spell-checking is case-insensitive
:) handles substrings properly
:( program is free of memory errors
    valgrind tests failed; see log for more information.

When I run valgrind this is what it gives me:

==347== 
==347== HEAP SUMMARY:
==347==     in use at exit: 472 bytes in 1 blocks
==347==   total heap usage: 143,096 allocs, 143,095 frees, 8,023,256 bytes allocated
==347== 
==347== 472 bytes in 1 blocks are still reachable in loss record 1 of 1
==347==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==347==    by 0x4A29AAD: __fopen_internal (iofopen.c:65)
==347==    by 0x4A29AAD: fopen@@GLIBC_2.2.5 (iofopen.c:86)
==347==    by 0x401B6E: load (dictionary.c:83)
==347==    by 0x4012CE: main (speller.c:40)
==347== 
==347== LEAK SUMMARY:
==347==    definitely lost: 0 bytes in 0 blocks
==347==    indirectly lost: 0 bytes in 0 blocks
==347==      possibly lost: 0 bytes in 0 blocks
==347==    still reachable: 472 bytes in 1 blocks
==347==         suppressed: 0 bytes in 0 blocks
==347== 
==347== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

This seems cryptic to me and I'm hoping someone can help explain and help me fix my problem (and Help50 doesn't have any advice).

Here is my actual code (please remember there is a second document with a main function that actually utilizes the functions provided, so it is ok for example that the functions seem to not be in proper order).

// Implements a dictionary's functionality

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

#include "dictionary.h"

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

// Number of buckets in hash table
const unsigned int N = 1000;

// Hash table
node *table[N];

// Dictionary size
int dictionary_size = 0;

// Returns true if word is in dictionary, else false
bool check(const char *word)
{
    // TODO #4!
    
    // make lowercase copy of word
    char copy[strlen(word) + 1];
    for (int i = 0; word[i]; i++)
    {
        copy[i] = tolower(word[i]);
    }
    copy[strlen(word)] = '\0';
    
    // get hash value
    int h = hash(copy);

    // use hash value to see if word is in bucket
    if (table[h] != NULL)
    {
        node *temp = table[h];
        
        while (temp != NULL)
        {
            if (strcmp(temp->word, copy) == 0)
            {
                return true;
            }
            
            temp = temp->next;
        }
    }
    
    return false;
}

// Hashes word to a number
unsigned int hash(const char *word)
{
    // TODO #2
    // source: https://www.reddit.com/r/cs50/comments/1x6vc8/pset6_trie_vs_hashtable/cf9189q/
    // I used this source because I had trouble understanding different variations - this one explained everything well.
    // I modified it slightly to fit my needs
    unsigned int h = 0;
    for (int i = 0; i < strlen(word); i++)
    {
        h = (h << 2) ^ word[i];
    }
    return h % N;
}

// Loads dictionary into memory, returning true if successful, else false
bool load(const char *dictionary)
{
    // TODO #1!
    // open dictionary file
    FILE *file = fopen(dictionary, "r");
    if (file == NULL)
    {
        return false;
    }
    
    // read strings from file one at a time
    char word[LENGTH + 1];
    while (fscanf(file, "%s", word) != EOF)
    {
        node *n = malloc(sizeof(node));
        if (n == NULL)
        {
            return false;
        }
        
        // place word into node
        strcpy(n->word, word);
        
        // use hash function to take string and return an index
        int h = hash(word);

        // make the current node point to the bucket we want
        n->next = table[h];
        
        // make the bucket start now with the current node
        table[h] = n;
        
        //count number of words loaded
        dictionary_size++;
    }

    return true;
}

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

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

In the same way that we must free every pointer that we malloc , we must fclose every FILE* that we fopen .

Your problem stems from this line:

FILE *file = fopen(dictionary, "r");

which has no corresponding fclose(file) call. Add this to the end of your loads function, before the return.

Valgrind can give very useful information for debugging (especially when your code is compiled with -g for debugging information), like in this excerpt from your question:

==347== 472 bytes in 1 blocks are still reachable in loss record 1 of 1
==347==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==347==    by 0x4A29AAD: __fopen_internal (iofopen.c:65)
==347==    by 0x4A29AAD: fopen@@GLIBC_2.2.5 (iofopen.c:86)
==347==    by 0x401B6E: load (dictionary.c:83)
==347==    by 0x4012CE: main (speller.c:40)

Valgrind is giving you the stack trace that allocated the memory that ended up leaking - you can see that the last line in your own code is dictionary.c:83 which is the line that calls fopen.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM