CS50 Pset5 Speller程序:Valgrind可运行但直接执行无响应
CS50 Pset5 Speller 程序冻结问题排查
我正在完成CS50的Pset5「speller」拼写检查任务,实现了字典加载到哈希表、拼写检查等功能,完成了load、hash、check、size、unload五个函数。现在遇到异常:用valgrind检查内存泄漏时,程序运行缓慢但功能正常且无泄漏;但直接运行或用debug50时,程序始终处于加载状态,无任何输出。已参考相关教程和帖子,未找到相同情况,希望定位代码缺陷。
完整代码
dictionary.c
// Implements a dictionary's functionality #include <ctype.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <string.h> #include "dictionary.h" // Represents a node in a hash table typedef struct node { char word[LENGTH + 1]; struct node *next; } node; // TODO: Choose number of buckets in hash table const unsigned int N = 26; unsigned int total_number = 0; // Hash table node *table[N]; // Returns true if word is in dictionary, else false bool check(const char *word) { // TODO int index = hash(word); node *temp = table[index]; while (temp != NULL) { if (strcasecmp(temp->word, word) == 0) { return true; } else { temp = temp->next; } } free(temp); return false; } // Hashes word to a number unsigned int hash(const char *word) { // TODO: Improve this hash function unsigned int index = 0; if (strlen(word) > 1) { unsigned int index1 = toupper(word[0]) - 'A'; unsigned int index2 = toupper(word[1]) - 'A'; index = index1 + index2; } else { index = toupper(word[0]) - 'A'; } index = index % N; return index; } // Loads dictionary into memory, returning true if successful, else false bool load(const char *dictionary) { // TODO // open dictionary FILE *file = fopen(dictionary, "r"); if (file == NULL) { printf("couldn't load dictionary\n"); return false; } // read word by word char word[LENGTH + 1]; while (fscanf(file, "%s", word) != EOF) { int index = hash(word); node *newnode = malloc(sizeof(node)); if (newnode == NULL) { printf("couldn't read file\n"); return false; } strcpy(newnode->word, word); newnode->next = table[index]; table[index] = newnode; total_number ++; free(newnode); } fclose(file); return true; } // Returns number of words in dictionary if loaded, else 0 if not yet loaded unsigned int size(void) { // TODO return total_number; } // Unloads dictionary from memory, returning true if successful, else false bool unload(void) { // TODO // iterate through the hash buckets for (int i = 0; i < N; i++) { // only free if bucket is used if (table[i] != NULL) { // setting up a moving cursor and node *tmp = table[i]; node *cursor = table[i]; while(cursor != NULL) { cursor = cursor->next; free(tmp); tmp = cursor; } free(cursor); } } return true; }
dictionary.h
// Declares a dictionary's functionality #ifndef DICTIONARY_H #define DICTIONARY_H #include <stdbool.h> // Maximum length for a word // (e.g., pneumonoultramicroscopicsilicovolcanoconiosis) #define LENGTH 45 // Prototypes bool check(const char *word); unsigned int hash(const char *word); bool load(const char *dictionary); unsigned int size(void); bool unload(void); #endif // DICTIONARY_H
speller.c
// Implements a spell-checker #include <ctype.h> #include <stdio.h> #include <sys/resource.h> #include <sys/time.h> #include "dictionary.h" // Undefine any definitions #undef calculate #undef getrusage // Default dictionary #define DICTIONARY "dictionaries/large" // Prototype double calculate(const struct rusage *b, const struct rusage *a); int main(int argc, char *argv[]) { // Check for correct number of args if (argc != 2 && argc != 3) { printf("Usage: ./speller [DICTIONARY] text\n"); return 1; } // Structures for timing data struct rusage before, after; // Benchmarks double time_load = 0.0, time_check = 0.0, time_size = 0.0, time_unload = 0.0; // Determine dictionary to use char *dictionary = (argc == 3) ? argv[1] : DICTIONARY; // Load dictionary getrusage(RUSAGE_SELF, &before); bool loaded = load(dictionary); getrusage(RUSAGE_SELF, &after); // Exit if dictionary not loaded if (!loaded) { printf("Could not load %s.\n", dictionary); return 1; } // Calculate time to load dictionary time_load = calculate(&before, &after); // Try to open text char *text = (argc == 3) ? argv[2] : argv[1]; FILE *file = fopen(text, "r"); if (file == NULL) { printf("Could not open %s.\n", text); unload(); return 1; } // Prepare to report misspellings printf("\nMISSPELLED WORDS\n\n"); // Prepare to spell-check int index = 0, misspellings = 0, words = 0; char word[LENGTH + 1]; // Spell-check each word in text char c; while (fread(&c, sizeof(char), 1, file)) { // Allow only alphabetical characters and apostrophes if (isalpha(c) || (c == '\'' && index > 0)) { // Append character to word word[index] = c; index++; // Ignore alphabetical strings too long to be words if (index > LENGTH) { // Consume remainder of alphabetical string while (fread(&c, sizeof(char), 1, file) && isalpha(c)); // Prepare for new word index = 0; } } // Ignore words with numbers (like MS Word can) else if (isdigit(c)) { // Consume remainder of alphanumeric string while (fread(&c, sizeof(char), 1, file) && isalnum(c)); // Prepare for new word index = 0; } // We must have found a whole word else if (index > 0) { // Terminate current word word[index] = '\0'; // Update counter words++; // Check word's spelling getrusage(RUSAGE_SELF, &before); bool misspelled = !check(word); getrusage(RUSAGE_SELF, &after); // Update benchmark time_check += calculate(&before, &after); // Print word if misspelled if (misspelled) { printf("%s\n", word); misspellings++; } // Prepare for next word index = 0; } } // Check whether there was an error if (ferror(file)) { fclose(file); printf("Error reading %s.\n", text); unload(); return 1; } // Close text fclose(file); // Determine dictionary's size getrusage(RUSAGE_SELF, &before); unsigned int n = size(); getrusage(RUSAGE_SELF, &after); // Calculate time to determine dictionary's size time_size = calculate(&before, &after); // Unload dictionary getrusage(RUSAGE_SELF, &before); bool unloaded = unload(); getrusage(RUSAGE_SELF, &after); // Abort if dictionary not unloaded if (!unloaded) { printf("Could not unload %s.\n", dictionary); return 1; } // Calculate time to unload dictionary time_unload = calculate(&before, &after); // Report benchmarks printf("\nWORDS MISSPELLED: %d\n", misspellings); printf("WORDS IN DICTIONARY: %d\n", n); printf("WORDS IN TEXT: %d\n", words); printf("TIME IN load: %.2f\n", time_load); printf("TIME IN check: %.2f\n", time_check); printf("TIME IN size: %.2f\n", time_size); printf("TIME IN unload: %.2f\n", time_unload); printf("TIME IN TOTAL: %.2f\n\n", time_load + time_check + time_size + time_unload); // Success return 0; } // Returns number of seconds between b and a double calculate(const struct rusage *b, const struct rusage *a) { if (b == NULL || a == NULL) { return 0.0; } else { return ((((a->ru_utime.tv_sec * 1000000 + a->ru_utime.tv_usec) - (b->ru_utime.tv_sec * 1000000 + b->ru_utime.tv_usec)) + ((a->ru_stime.tv_sec * 1000000 + a->ru_stime.tv_usec) - (b->ru_stime.tv_sec * 1000000 + b->ru_stime.tv_usec))) / 1000000.0); } }
Makefile
speller: clang -ggdb3 -gdwarf-4 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-gnu-folding-constant -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -c -o speller.o speller.c clang -ggdb3 -gdwarf-4 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-gnu-folding-constant -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -c -o dictionary.o dictionary.c clang -ggdb3 -gdwarf-4 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-gnu-folding-constant -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -o speller speller.o dictionary.o -lm
问题定位与修复
1. load函数的致命错误
在load函数中,创建新节点后立即调用free(newnode),直接释放了刚分配的内存,导致哈希表中存储的是已释放的野指针。后续check函数访问这些野指针时触发未定义行为,普通运行环境中表现为程序冻结;valgrind对野指针有特殊处理,因此能勉强运行但速度极慢。
修复:删除load函数中的free(newnode)语句,节点释放应放在unload函数中完成。
修正后的load函数循环部分:
while (fscanf(file, "%s", word) != EOF) { int index = hash(word); node *newnode = malloc(sizeof(node)); if (newnode == NULL) { printf("couldn't read file\n"); return false; } strcpy(newnode->word, word); newnode->next = table[index]; table[index] = newnode; total_number ++; // 移除错误的free语句 }
2. check函数的多余释放
check函数末尾的free(temp)是多余的,此时temp已经是NULL,释放空指针虽不会崩溃,但属于无效操作,应删除。
修正后的check函数末尾:
while (temp != NULL) { if (strcasecmp(temp->word, word) == 0) { return true; } else { temp = temp->next; } } // 移除多余的free语句 return false;
3. unload函数的多余释放
unload函数循环结束后cursor已经是NULL,此时调用free(cursor)属于无效操作,应删除。
修正后的unload函数循环部分:
while(cursor != NULL) { cursor = cursor->next; free(tmp); tmp = cursor; } // 移除多余的free语句
内容的提问来源于stack exchange,提问作者user75470
相关产品推荐
相关产品推荐

