问题描述
我刚刚完成了cs50 pset 5 speller代码。它不是完美而高效的,但我首先要使其工作。该代码对给定词典中的文本进行拼写检查。该程序将按预期运行。我在上面使用valgrind来检查内存泄漏,并显示以下内容
==12518== Memcheck,a memory error detector
==12518== Copyright (C) 2002-2017,and GNU GPL'd,by Julian Seward et al.
==12518== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==12518== Command: ./speller texts/lalaland.txt
==12518==
==12518== Conditional jump or move depends on uninitialised value(s)
==12518== at 0x524A071: _IO_vfscanf (vfscanf.c:1021)
==12518== by 0x52562E5: __isoc99_fscanf (isoc99_fscanf.c:34)
==12518== by 0x4011FC: load (dictionary.c:67)
==12518== by 0x4009B4: main (speller.c:40)
==12518== Uninitialised value was created by a stack allocation
==12518== at 0x401194: load (dictionary.c:59)
==12518==
==12518== Use of uninitialised value of size 8
==12518== at 0x5246FDD: _IO_vfscanf (vfscanf.c:1103)
==12518== by 0x52562E5: __isoc99_fscanf (isoc99_fscanf.c:34)
==12518== by 0x4011FC: load (dictionary.c:67)
==12518== by 0x4009B4: main (speller.c:40)
==12518== Uninitialised value was created by a stack allocation
==12518== at 0x401194: load (dictionary.c:59)
==12518==
==12518==
==12518== Process terminating with default action of signal 11 (SIGSEGV)
==12518== Bad permissions for mapped region at address 0x51DF0E8
==12518== at 0x5246FDD: _IO_vfscanf (vfscanf.c:1103)
==12518== by 0x52562E5: __isoc99_fscanf (isoc99_fscanf.c:34)
==12518== by 0x4011FC: load (dictionary.c:67)
==12518== by 0x4009B4: main (speller.c:40)
==12518==
==12518== HEAP SUMMARY:
==12518== in use at exit: 552 bytes in 1 blocks
==12518== total heap usage: 2 allocs,1 frees,4,648 bytes allocated
==12518==
==12518== 552 bytes in 1 blocks are still reachable in loss record 1 of 1
==12518== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12518== by 0x5258E49: __fopen_internal (iofopen.c:65)
==12518== by 0x5258E49: fopen@@GLIBC_2.2.5 (iofopen.c:89)
==12518== by 0x4011B1: load (dictionary.c:60)
==12518== by 0x4009B4: main (speller.c:40)
==12518==
==12518== LEAK SUMMARY:
==12518== definitely lost: 0 bytes in 0 blocks
==12518== indirectly lost: 0 bytes in 0 blocks
==12518== possibly lost: 0 bytes in 0 blocks
==12518== still reachable: 552 bytes in 1 blocks
==12518== suppressed: 0 bytes in 0 blocks
==12518==
==12518== For counts of detected and suppressed errors,rerun with: -v
==12518== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Segmentation fault
我是一名初学者,从中我了解到我没有在某个地方释放552个字节。下面是我的代码:
// Implements a dictionary's functionality
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <stdlib.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 = 5000;
// Hash table
node *table[N];
int words = 0;
// Returns true if word is in dictionary else false
bool check(const char *word)
{
int x = hash(word);
node *cursor = table[x];
while (cursor != NULL)
{
if (strcasecmp (cursor->word,word) == 0)
{
return true;
}
cursor = cursor->next;
}
return false;
}
// Hashes word to a number
unsigned int hash(const char *word)
{
int x = 0;
for (int i = 0,n = strlen(word); i < n; i++)
{
char a;
a = toupper(word[i]);
x += a;
}
return x;
}
// Loads dictionary into memory,returning true if successful else false
bool load(const char *dictionary)
{
FILE *file = fopen(dictionary,"r");
if (file == NULL)
{
printf("Dictionary couldn't be opened\n");
return false;
}
char *word[LENGTH + 1];
while (fscanf(file,"%s",*word) != EOF)
{
node *n = malloc(sizeof(node));
if (n == NULL)
{
printf("Out of memory.\n");
return false;
}
strcpy (n->word,*word);
n->next = NULL;
int x = hash(*word);
if (table[x] == NULL)
{
table[x] = n;
}
else
{
n->next = table[x];
table[x] = n;
}
words++;
}
fclose(file);
return true;
}
// Returns number of words in dictionary if loaded else 0 if not yet loaded
unsigned int size(void)
{
return words;
}
// Unloads dictionary from memory,returning true if successful else false
bool unload(void)
{
for (int i = 0; i < N; i++)
{
if (table[i] != NULL)
{
node *cursor = table[i];
node *tmp = table[i];
while (cursor != NULL)
{
tmp = cursor;
cursor = cursor->next;
free(tmp);
}
}
}
return true;
}
有人可以帮助我找出问题所在吗?
另外请注意,我运行check50来检查它是否可以正常运行,但没有通过,我得到了this result。我猜这与内存泄漏有关,但我不确定。
解决方法
仔细查看日志,它们实际上具有非常有用的信息。它告诉我您正在引用一个未初始化的值。实际上,这可能会导致SEGFAULT。这在日志中得到确认:
==12518== Process terminating with default action of signal 11 (SIGSEGV)
让我们看看如何解决这个问题。日志的相关部分是:
==12518== Conditional jump or move depends on uninitialised value(s)
==12518== at 0x524A071: _IO_vfscanf (vfscanf.c:1021)
==12518== by 0x52562E5: __isoc99_fscanf (isoc99_fscanf.c:34)
==12518== by 0x4011FC: load (dictionary.c:67)
==12518== by 0x4009B4: main (speller.c:40)
==12518== Uninitialised value was created by a stack allocation
==12518== at 0x401194: load (dictionary.c:59)
此日志的第一部分是回溯。它显示导致错误的函数调用顺序。在这种情况下,main
称为load
,后者称为<something>_fscanf
。回溯的其余部分用于实现libc
的{{1}}中的内部调用。这告诉我们错误所在的位置。它还告诉我什么错误是:程序使用值而不初始化它。
现在,让我们看一下fscanf
函数中的相关行:
load
其中,我可以看到 while (fscanf(file,"%s",*word) != EOF) { ... }
已通过调用file
并检查返回的空值来正确初始化。第二个参数是一个常量字符串。因此,第三个论点是问题。让我们看看它的声明位置。
现在,在上一行中,您看到fopen
被声明为word
,它是一个字符指针数组。我想您实际上想要的是指向char数组的指针。正确的声明应为char *word[]
。在当前形式中,它是一个指针数组。这些指针尚未在此函数中初始化,因此实际上可能包含char word[LENGTH + 1]
空指针。此外,由于堆栈重用(请参阅堆栈帧如何工作),此数组可能包含其他随机垃圾,其中可能包括实际的指针(并导致状态损坏)。实际上,这正是发生的情况。让我们回到日志:
LENGTH + 1
未初始化的指针保存着值==12518== Process terminating with default action of signal 11 (SIGSEGV)
==12518== Bad permissions for mapped region at address 0x51DF0E8
,并且0x51df0e8
可能试图写入该地址,由于指针实际上是无效的,因此遇到了分段错误。
最后,让我们看一下日志的最后一部分:
fscanf
这告诉我们一些分配的堆内存没有被释放。这是您的细分错误的结果。 ==12518== HEAP SUMMARY:
==12518== in use at exit: 552 bytes in 1 blocks
已执行,但没有执行相应的malloc
,因为程序在两者之间崩溃了。
最后,我想强调指出,读取和解释包含许多有用信息的日志非常有用。此外,我还建议您学习使用free
之类的调试器,该调试器将使您能够快速而准确地识别程序中的问题,并且随着程序的增长而变得必不可少。 Bon编码,厨师...
希望LENGTH
在dictionary.h
中定义,并且比文件中的任何单词长
char word[LENGTH + 1]; // not char *word[LENGTH + 1];
返回之前请记住close(file)
。
size_t table_index = x % N; //need table_index between 0 and N-1
如果更改表的声明方式,请记住在开始时将表归零。