C语言CS50 pset5 speller运行提示killed报错问题求助
做CS50 pset5的speller练习时程序异常:运行后终端输出killed,尝试调试、用valgrind做内存检测时程序直接停止退出,无法正常运行。
原实现代码如下:
#include <ctype.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.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 = 150000; // Hash table node *table[N]; //Declare variables here so that they can be used in the different functions below unsigned int HASH_INDEX; unsigned int NO_OF_WORDS = 0; node *CURSOR; // Returns true if word is in dictionary, else false bool check(const char *word) { // TODO HASH_INDEX = hash(word); CURSOR = table[HASH_INDEX]; do { if (strcasecmp(CURSOR->word, word) == 0) { return true; } else { CURSOR = CURSOR->next; } } while (CURSOR != NULL); return false; } // Hashes word to a number unsigned int hash(const char *word) { // TODO: Improve this hash function // close address, each bucket in the hash table is a pointer unsigned int hash_value = 0; for (int i = 0; i < strlen(word); i++) { int c = tolower(word[i]); hash_value = hash_value + c; } hash_value = hash_value % 31; return hash_value; } // Loads dictionary into memory, returning true if successful, else false bool load(const char *dictionary) { // TODO // open dictionary file FILE *d = fopen(dictionary, "r"); if (d == NULL) { return false; } char word[LENGTH + 1]; while (fscanf(d, "%s", word) != EOF) { node *w = malloc(sizeof(node)); if (w == NULL) { return false; } else { strcpy(w->word, word); w->next = NULL; HASH_INDEX = hash(word); if (table[HASH_INDEX] == NULL) { table[HASH_INDEX] = w; } w->next = table[HASH_INDEX]; table[HASH_INDEX] = w; NO_OF_WORDS++; } } fclose(d); return true; } // Returns number of words in dictionary if loaded, else 0 if not yet loaded unsigned int size(void) { return NO_OF_WORDS; } // Unloads dictionary from memory, returning true if successful, else false bool unload(void) { for (int i = 0; i < N; i++) { CURSOR = table[i]; while (CURSOR != NULL) { node *tmp = CURSOR; CURSOR = CURSOR->next; free(tmp); } } return true; }
程序输出killed是被系统发送SIGKILL信号强制终止导致的,核心原因是代码存在多个逻辑bug,引发无限死循环占满系统资源,同时还有其他会触发崩溃的隐患:
核心bug:哈希表插入逻辑错误,产生链表自环引发死循环
插入节点时多余的if判断会导致每个哈希桶里第一个插入的节点next指针指向自身,形成自环:// 错误逻辑 if (table[HASH_INDEX] == NULL) { table[HASH_INDEX] = w; } w->next = table[HASH_INDEX]; table[HASH_INDEX] = w;当桶为空时,if块内先把桶头指针指向新节点
w,随后执行w->next = table[HASH_INDEX]等价于w->next = w,节点自引用。后续遍历链表到这个节点时,指针永远不会走到NULL,陷入无限死循环,长时间占用CPU资源最终被系统强制杀死。
头插法插入链表不需要这个if判断,直接写两行即可:w->next = table[HASH_INDEX]; table[HASH_INDEX] = w;check函数存在空指针解引用风险
用do...while循环遍历链表会先执行循环体再判断终止条件,如果对应哈希桶为空(桶头指针为NULL),进入循环第一行就会访问CURSOR->word,直接触发空指针解引用的段错误。另外CURSOR、HASH_INDEX这类临时变量没必要设为全局变量,容易出现跨函数的变量污染,改成函数内局部变量即可,遍历逻辑改成先判空再访问:bool check(const char *word) { unsigned int hash_idx = hash(word); node *cursor = table[hash_idx]; while (cursor != NULL) { if (strcasecmp(cursor->word, word) == 0) { return true; } cursor = cursor->next; } return false; }unload函数里的
CURSOR也建议改成局部变量,不要复用全局变量。哈希函数返回值范围不合理,冲突严重
定义了150000个哈希桶,但哈希函数最后用31取模,所有单词只会落到0~30共31个桶里,剩下的桶完全闲置,哈希冲突极多,链表过长会大幅降低程序运行效率。把取模值改成和桶数量一致即可:hash_value = hash_value % N;,也可以替换成分布更均匀的哈希函数。存在缓冲区溢出风险
用fscanf(d, "%s", word)读取单词时没有限制最大读取长度,一旦遇到长度超过LENGTH的字符串就会溢出word数组,破坏栈内存引发未定义行为,改成fscanf(d, "%45s", word)(和LENGTH定义的长度匹配)限制读取长度即可。
把以上问题修复后,程序就能正常运行,不会再出现killed或者崩溃的问题。
内容的提问来源于stack exchange,提问作者jj7

