我为前面的长代码片段道歉,但我花了很长时间在这里看,我觉得到目前为止我所看到的并不能帮助我解决这个问题。我已经在课程论坛上提问,有过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;像这样,但只知道那是不我正在寻找的解决方案,因为我知道还有一些其他根本原因(即导致它无法在任何设备上工作的原因...... 。)
同样,如果您能以任何方式帮助解决我的逻辑错误,我将非常感激。我一直试图弄清楚这几个小时无济于事。
答案 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开始抛出各种错误(再次)。所以集中你的调试工作。