无法修复的内存泄漏

时间:2016-10-10 12:33:39

标签: c memory memory-management memory-leaks trie

我为前面的长代码片段道歉,但我花了很长时间在这里看,我觉得到目前为止我所看到的并不能帮助我解决这个问题。我已经在课程论坛上提问,有过TA的帮助,并且得到了朋友的建议,但没有任何东西可以解决问题的根源。

在这个程序中,我使用树来创建一个拼写检查器。在我的代码中有许多事情需要修复,但内存泄漏是我真正需要帮助解决的问题。

问题在于我很确定我为节点分配了正确的空间量,我认为Valgrind确认了这一点,因为我只有2个未释放的块(365,371个分配中)。

无论如何,我将发布整个代码(以防任何人需要完整的上下文),但我认为相关的部分是加载函数和清除函数,我分别分配和释放内存。

/**
c* Implements a dictionary's functionality.
*/
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dictionary.h"

// number of characters we are using (a-z and ')
#define LETTERS 27

// max guaranteed number of nonnegative char values that exist
#define CHARVALUES 128

// create node structure for trie
typedef struct node
{
    struct node *children[LETTERS];
    bool is_word;
}
node;

// create root node for trie
node *root;

// stores the size of our dictionary
unsigned int dict_size = 0;

/**
 * Returns true if word is in dictionary else false.
 */
bool check(const char *word)
{
    // keeps track of where we are; starts with root for each new word
    node *current_node = root;

    while (*word != '\0')
    {

        // indices: 'a' -> 0, ..., 'z' -> 25, '\' -> 26
        int index = (tolower(*word) - 'a') % CHARVALUES;
        if (index >= LETTERS - 1)
        {
            // by assumption, the char must be '\'' if not '\n' or a letter
            index = LETTERS - 1;
        }

        // if the node we need to go to is NULL, the word is not here
        if (current_node->children[index] == NULL)
        {
            return false;
        }

        // go to the next logical node, and look at the next letter of the word
        current_node = current_node->children[index];
        word++;
    }
    return current_node->is_word;
}

/**
 * Loads dictionary into memory. Returns true if successful else false.
 */
bool load(const char *dictionary)
{

    FILE *inptr = fopen(dictionary, "r");
    if (inptr == NULL)
    {
        return false;
    }

    // allocate memory for the root node
    root = malloc(sizeof(node));

    // store first letter (by assumption, it must be a lowercase letter)
    char letter = fgetc(inptr);

    // stores indices corresponding to letters
    int index = 0;

    /**
     * we can assume that there is at least one word; we will execute the loop
     * and assign letter a new value at the end. at the end of each loop, due
     * to the inside loop, letter will be a newline; we know the EOF in the
     * dictionary follows a newline, so the loop will terminate appropriately
     */
    do
    {
        // keeps track of where we are; starts with root for each new word
        node *current_node = root; 

        // this loop will only execute if our character is a letter or '\''
        while (letter != '\n')
        {
            // indices: 'a' -> 0, ..., 'z' -> 25, '\' -> 26
            index = (letter - 'a') % CHARVALUES;
            if (index >= LETTERS - 1)
            {
                // by assumption, the char must be '\'' if not '\n' or a letter
                index = LETTERS - 1;
            }

            // allocate memory for a node if we have not done so already
            if (current_node->children[index] == NULL)
            {
                current_node->children[index] = malloc(sizeof(node));

                // if we cannot allocate the memory, unload and return false
                if (current_node->children[index] == NULL)
                {
                    unload();
                    return false;
                }

            }

            // go to the appropriate node for the next letter in our word
            current_node = current_node->children[index];

            // get the next letter
            letter = fgetc(inptr);
        }

        // after each linefeed, our current node represents a dictionary word
        current_node->is_word = true;
        dict_size++;

        // get the next letter
        letter = fgetc(inptr);
    }
    while (letter != EOF);

    fclose(inptr);

    // if we haven't returned false yet, then loading the trie must have worked
    return true;
}

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

void clear(node *head)
{
    for (int i = 0; i < LETTERS; i++)
    {
        if (head->children[i] != NULL)
        {
            clear(head->children[i]);
        }
    }
    free(head);
}

    /**
     * Unloads dictionary from memory. Returns true if successful else false.
     */
    bool unload(void)
    {
        clear(root);
        return true;
    }

相关的valgrind输出如下:

==18981== HEAP SUMMARY:
==18981==     in use at exit: 448 bytes in 2 blocks
==18981==   total heap usage: 365,371 allocs, 365,369 frees, 81,843,792 bytes allocated
==18981== 
==18981== 448 (224 direct, 224 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==18981==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==18981==    by 0x4011B0: load (dictionary.c:111)
==18981==    by 0x4008CD: main (speller.c:40)
==18981== 
==18981== LEAK SUMMARY:
==18981==    definitely lost: 224 bytes in 1 blocks
==18981==    indirectly lost: 224 bytes in 1 blocks
==18981==      possibly lost: 0 bytes in 0 blocks
==18981==    still reachable: 0 bytes in 0 blocks
==18981==         suppressed: 0 bytes in 0 blocks
==18981== 1 errors in context 3 of 11:
==18981== 
==18981== 
==18981== Invalid read of size 8
==18981==    at 0x40120C: load (dictionary.c:123)
==18981==    by 0x4008CD: main (speller.c:41)
==18981==  Address 0xb3fde70 is 16 bytes before a block of size 224 alloc'd
==18981==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==18981==    by 0x4011CB: load (dictionary.c:111)
==18981==    by 0x4008CD: main (speller.c:41)
==18981== 
==18981== 
==18981== 1 errors in context 4 of 11:
==18981== Invalid read of size 8
==18981==    at 0x4011E0: load (dictionary.c:114)
==18981==    by 0x4008CD: main (speller.c:41)
==18981==  Address 0xb3fde70 is 16 bytes before a block of size 224 alloc'd
==18981==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==18981==    by 0x4011CB: load (dictionary.c:111)
==18981==    by 0x4008CD: main (speller.c:41)
==18981== 
==18981== 
==18981== 1 errors in context 5 of 11:
==18981== Invalid write of size 8
==18981==    at 0x4011D4: load (dictionary.c:111)
==18981==    by 0x4008CD: main (speller.c:41)
==18981==  Address 0xb3fde70 is 16 bytes before a block of size 224 alloc'd
==18981==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==18981==    by 0x4011CB: load (dictionary.c:111)
==18981==    by 0x4008CD: main (speller.c:41)
==18981== 
==18981== 
==18981== 1 errors in context 6 of 11:
==18981== Invalid read of size 8
==18981==    at 0x4011B2: load (dictionary.c:109)
==18981==    by 0x4008CD: main (speller.c:41)
==18981==  Address 0xb3fde70 is 16 bytes before a block of size 224 alloc'd
==18981==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==18981==    by 0x4011CB: load (dictionary.c:111)
==18981==    by 0x4008CD: main (speller.c:41)

所以,我对这个输出的解释是,在下面的代码块中:

        if (current_node->children[index] == NULL)
        {
            current_node->children[index] = malloc(sizeof(node));

            // if we cannot allocate the memory, unload and return false
            if (current_node->children[index] == NULL)
            {
                unload();
                return false;
            }

        }

malloc语句(实际上是line dictionary.c:111)执行两次,以便永远不会释放分配的内存。 (这是正确的吗?)现在,这让我认为真正的问题在于我的明确功能,即它写得不好而且不能清除我的每个节点。

但是,我已经盯着代码几个小时,我几乎看不出它有什么问题。 (我确信很多;我对此并不太擅长。)

对此的任何帮助将不胜感激。

作为旁注:我有多个人(不是课程人员)告诉我,我应该将children数组中的所有指针初始化为NULL,但课程工作人员直接告诉我这是可选的,我已经用相同的结果测试了两种方式。我知道它可能是一种便携性的东西,即使它在技术上和#34;工作&#34;像这样,但只知道那是我正在寻找的解决方案,因为我知道还有一些其他根本原因(即导致它无法在任何设备上工作的原因...... 。)

同样,如果您能以任何方式帮助解决我的逻辑错误,我将非常感激。我一直试图弄清楚这几个小时无济于事。

2 个答案:

答案 0 :(得分:4)

root = malloc(sizeof(node));

这会产生一大块未初始化的内存。

if (current_node->children[index] == NULL)

这里假设内存已经初始化,而实际上是垃圾。

您需要在使用它们之前初始化root的内容,或者使用calloc将它们全部设置为零。

答案 1 :(得分:0)

用calloc()切换两个malloc()语句后(正如其他人所建议的那样;这会删除你的其他许多valgrind错误),添加一个小样本字典,以及下面的极简主义():

int main() {

    load("dict.txt");

    printf("Checked: %i\n", check("hello"));
    printf("Checked: %i\n", check("sdfsdf"));

    unload();

    return 0;
}

...您的代码运行得更干净,没有任何内存泄漏:

==636== HEAP SUMMARY:
==636==     in use at exit: 0 bytes in 0 blocks
==636==   total heap usage: 15 allocs, 15 frees, 42,688 bytes allocated
==636==
==636== All heap blocks were freed -- no leaks are possible
==636==
==636== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 12 from 8)

你会遇到一个明显的泄漏,如果你从load()返回false - 你不会释放文件指针。

编辑:当你向字典中引入大写单词时,Valgrind开始抛出各种错误(再次)。所以集中你的调试工作。