C 不可修复内存泄漏
很抱歉前面有这么长的代码片段,但是我花了很长时间在这里查看,我觉得到目前为止我所看到的任何东西都不能帮助我解决这个问题。我在课程论坛上提出了问题,得到了助教的帮助,也从朋友那里得到了建议,但没有任何东西能够锁定我问题的根源 在这个程序中,我使用一棵树来创建拼写检查器。我的代码中有很多东西需要修复,但内存泄漏是我唯一真正需要帮助解决的问题 问题是,我相当确定我为节点分配了正确的空间量,我认为Valgrind证实了这一点,因为我只有2个未释放的块(365371个alloc中) 无论如何,我将发布整个代码(以防任何人需要完整的上下文),但我认为相关的部分是load函数和clear函数,在这里我分别分配和释放内存C 不可修复内存泄漏,c,memory,memory-management,memory-leaks,trie,C,Memory,Memory Management,Memory Leaks,Trie,很抱歉前面有这么长的代码片段,但是我花了很长时间在这里查看,我觉得到目前为止我所看到的任何东西都不能帮助我解决这个问题。我在课程论坛上提出了问题,得到了助教的帮助,也从朋友那里得到了建议,但没有任何东西能够锁定我问题的根源 在这个程序中,我使用一棵树来创建拼写检查器。我的代码中有很多东西需要修复,但内存泄漏是我唯一真正需要帮助解决的问题 问题是,我相当确定我为节点分配了正确的空间量,我认为Valgrind证实了这一点,因为我只有2个未释放的块(365371个alloc中) 无论如何,我将发布整个
/**
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;
}
因此,我对这个输出的解释是,在下面的代码块中:
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)两次,这样分配的内存永远不会被释放。(这是正确的吗?)现在,这让我认为真正的问题在于我的clear函数,也就是说,它写得不好,不能清除trie的每个节点
然而,我已经盯着代码看了好几个小时,我真的看不出它有什么问题。(我相信很多人都是这样;我只是不太擅长这个。)
在此方面的任何帮助都将不胜感激
作为旁注:我有很多人(不是课程工作人员)告诉我应该将children数组中的所有指针初始化为NULL,但课程工作人员直接告诉我这是可选的,我已经用相同的结果对这两种方法进行了测试。我知道这可能是一个可移植性的东西,即使它在技术上“工作”像那样,但我只知道这不是我正在寻找的解决方案,因为我知道还有一些其他的根本原因(即,一个导致它在任何设备上都不工作的原因…)
同样,如果你能以任何方式帮助我解决我的逻辑问题,我将不胜感激。我花了几个小时试图弄明白这一点,但毫无结果
root = malloc(sizeof(node));
这会产生一块未初始化的内存
if (current_node->children[index] == NULL)
这里假设内存已经初始化,而实际上是垃圾
您需要在使用root
之前初始化它们的内容,或者使用calloc将它们全部设置为零
这会产生一块未初始化的内存
if (current_node->children[index] == NULL)
这里假设内存已经初始化,而实际上是垃圾
在使用
root
之前,您需要初始化它们的内容,或者使用calloc将它们全部设置为零。在使用calloc()切换malloc()语句后(如其他人所建议,这将删除大量valgrind错误),添加一个小样本字典和以下最小值main()
。。。您的代码运行更干净,没有任何内存泄漏:
==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开始(再次)抛出各种各样的错误。因此,请集中精力进行调试 在使用calloc()切换两个malloc()语句之后(正如其他人所建议的;这将删除大量valgrind错误),添加一个小样本字典,以及以下最小值main(): 。。。您的代码运行更干净,没有任何内存泄漏:
==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开始(再次)抛出各种各样的错误。因此,请集中精力进行调试 故意的。正如我所提到的,我测试了这个程序,不管有没有指针初始化(对于根数组和所有子数组),结果都是一样的。实际上,我们是在运行时排名的,所以从技术上讲,只要它编译并“工作”而不泄漏内存,我们就被鼓励删减任何不必要的内容,所以我删减了它。我会承认,这是一种“错误”的发展方式,一般来说,是的,这正是这个问题,在我开始考虑它不固定之前,担心速度。记住优化的基本规则:(1)不要这样做!(2) (仅限专家)暂时不要这样做。代码中有明显的越界读取,而您的问题是关于泄漏的?故意的。正如我所提到的,我测试了这个程序,不管有没有指针初始化(对于根数组和所有子数组),结果都是一样的。实际上,我们是在运行时排名的,所以从技术上讲,只要它编译并“工作”而不泄漏内存,我们就被鼓励删减任何不必要的内容,所以我删减了它。我会承认,这是一种“错误”的发展方式,一般来说,是的,这正是这个问题,在我开始考虑它不固定之前,担心速度。记住优化的基本规则:(1)不要这样做!(2) (仅限专家使用)不要这样做。代码中存在明显的越界读取,而您的问题是关于泄漏的?正如我所说的,我已经测试了代码,包括是否将这些指针初始化为NULL。我还测试了使用和不使用calloc的情况。它除了可移植性之外什么都没有帮助,实际上这对于这个任务是不鼓励的,因为它不是必需的,并且会显著增加运行时间。这不是我的内存错误的根源(至少在代码编译的机器上,这是这里最重要的),考虑到我在采取这些预防措施时会遇到完全相同的错误。@mattstone
malloc
:ed内存的内容可以是任何垃圾。这不是一个po