【发布时间】:2016-10-10 12:33:38
【问题描述】:
对于前面的长代码sn-p,我深表歉意,但我花了很长时间看这里,我觉得到目前为止我所看到的没有任何东西可以帮助我解决这个问题。我在课程论坛上提问过,得到了 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 语句(实际上是行 dictionary.c:111)被执行两次,这样分配的内存就永远不会被释放。 (这是正确的吗?)现在,这让我认为真正的问题在于我的 clear 函数,即它写得不好并且没有清除我的 trie 的每个节点。
但是,我已经盯着代码看了好几个小时,我真的看不出它有什么问题。 (我敢肯定很多;我只是不太擅长这个。)
对此的任何帮助将不胜感激。
作为旁注:我有多个人(不是课程工作人员)告诉我应该将孩子数组中的所有指针初始化为 NULL,但课程工作人员直接告诉我这是可选的,我已经两种方式都进行了测试,结果相同。我知道这可能是一个可移植性的东西,即使它在技术上是这样“工作”的,但只知道那不是我正在寻找的解决方案,因为我知道还有其他一些根本原因(即一个这导致它根本无法在任何设备上运行......)
再次,如果您能以任何方式帮助解决我的逻辑问题,我将不胜感激。我一直试图解决这个问题几个小时都无济于事。
【问题讨论】:
-
故意的。正如我所提到的,我在使用和不使用指针初始化(针对根数组和所有子数组)的情况下测试了程序,结果相同。
-
实际上,我们是在运行时排名的,所以从技术上讲,只要它可以编译并且“工作”而不会泄漏内存,我们就被鼓励削减任何不必要的东西,所以我削减了它。不过,我承认这是一种“错误”的总体开发方法。
-
是的,这正是这个问题,在我开始认为它无法修复之前
-
在担心速度之前先做好准备。记住优化的基本规则:(1)不要这样做! (2) (仅限专家)不要这样做。
-
您在代码中有明显的越界读取,您的问题是关于泄漏?
标签: c memory memory-management memory-leaks trie