尝试从文件中读取单词列表并存储到 C 中的数组中

Trying to read list of words from file and store into an array in C

我的目标是逐行读取我的文本文件 "dictionary.txt" 并将其中的所有单词暂时保存到名为 words 的数组中,并想打印它以确保数组包含除我之外的所有内容我是 C 的新手,不确定如何去做。当我尝试 运行 时,程序崩溃了,但不确定为什么。
(这是一个相当大的文本文件,包含149256个单词,最长的单词为20个字符)

编辑:我想动态分配数组,不确定是否可以这样做。

#include <stdio.h>
#include <stdlib.h>
#define listlength 149256
#define wordslength 21


char** getwords(int rows, int col);
void freeArray(char** words, int rows);

int main(){

    int i,j, numCases;
    char** words = getwords(listlength, wordslength);
    //test to see if the words array is saving correctly

    for(i=0;i<20;i++){
        printf("%s", words[i]);
    }

    //Get number of cases.
    //printf("enter number of cases:\n");
    //scanf("%d", &numCases);
    //Process each case.

    freeArray(words, listlength);

}


char** getwords(int rows, int col){

    //allocate top level of pointers.

    char** words = malloc(sizeof(char*)*rows);
    int i;
    FILE *dictionary;

    //allocate each individual array
    for(i=0; i<rows; i++){
        words[i] = malloc(sizeof(char)*col);
    }
        //read dictionary.txt
    for(i=0; i<rows; i++){
        FILE *dictionary = fopen("dictionary.txt", "r");
        fgets(words[i],wordslength,dictionary);
    }

    fclose(dictionary);
    return words;
}

void freeArray(char** words, int rows){

    int i;
    for(i=0; i<rows; i++){
        free(words[i]);
    }
    free(words);
}

好吧,我想我不会在这里列出所有错误 ;),我会为您重写 getwords 函数,并希望一路教给您。请注意,我在这里做了一些假设。我假设文件每行一个字,最大长度是 cols 参数。首先,我会将参数名称更改为 maxWordLen 而不是 cols(这更清楚),并将 getwords 更改为 getWords(这是惯例)。像这样制作函数签名:

char** getWords(int rows, int maxWordLen)

你可以直接去掉这两行:

int i;
FILE *dictionary;

为了分配,您需要在每个字符串的末尾包含 space 作为空字符。

//   VVV put declaration here (on others as well)
for (int i = 0; i < rows; i++) {
    words[i] = malloc(sizeof(char) * (maxWordLen + 1));
    //                                          ^^^^
}

请勿多次打开文件!!!您的代码:

for(i=0; i<rows; i++){
    FILE *dictionary = fopen("dictionary.txt", "r");
    fgets(words[i],wordslength,dictionary);
}

它不仅不起作用,因为它每次都从文件的顶部开始,这是不好的做法,而且内存效率非常低。改为这样做:

FILE* dictionary = fopen("dictionary.txt", "r");

for (int i = 0; i < rows; i++) {
    fgets(words[i], maxWordLen + 1, dictionary);
}

最后两行很好,只需关​​闭文件即可完成 return words。哇!这是所有这些的浓缩代码片段 ;):

char** getWords(int rows, int maxWordLen) {
    char** words = malloc(sizeof(char*) * rows);

    for (int i = 0; i < rows; i++) {
        words[i] = malloc(sizeof(char) * (maxWordLen + 1));
    }

    FILE* dictionary = fopen("dictionary.txt", "r");

    for (int i = 0; i < rows; i++) {
        fgets(words[i], maxWordLen + 1, dictionary);
    }

    fclose(dictionary);

    return words
}

现在我还没有测试这段代码,所以它可能有一些拼写错误,但希望这对你有所帮助!

您在确定要传递给 getwords 的重要内容时遇到了一些困难。虽然您可以 embed/hardcode 函数中的文件名,但这确实违背了创建灵活的可重用路由的目的。当您考虑函数需要什么时,它需要 (1) 一个 FILE* 流来读取; (2) 一种将 return 字数读入 指针到指针 字符串的方法; (3) 它必须 return 指针。这样你就可以返回新分配的 words 列表并知道有多少。

您对 fgets 的使用有点尴尬。由于您已将 wordslength 定义为 21,因此您可以简单地静态声明 wordslength + 1 的缓冲区(例如 buf)以与 fgets 一起使用,然后 allocate/copy 到 words[i]。这使您可以确保在分配内存之前 buf 中有一个有效的字符串。

最后,有一个 realloc 函数使得不需要一次分配所有 149256 指针。 (如果你知道 你将拥有多少,那很好)作为一般规则,从一些合理的预期数量开始,然后在达到限制时 realloc 额外的指示继续前进。

这里是一个快速重写,将各个部分放在一起:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define listlength 256
#define wordslength 21

char **getwords (FILE *fp, int *n);
void free_array (char** words, int rows);

int main (int argc, char **argv) {

    int i, nwords = 0;
    char **words = NULL;  /* file given as argv[1] (default dictionary.txt) */
    char *fname = argc > 1 ? argv[1] : "dictionary.txt";
    FILE *dictionary = fopen (fname, "r");

    if (!dictionary) { /* validate file open */
        fprintf (stderr, "error: file open failed.\n");
        return 1;
    }

    if (!(words = getwords (dictionary, &nwords))) {
        fprintf (stderr, "error: getwords returned NULL.\n");
        return 1;
    }
    fclose(dictionary);

    printf ("\n '%d' words read from '%s'\n\n", nwords, fname);

    for (i = 0; i < nwords; i++) {
        printf ("%s\n", words[i]);
    }

    free_array (words, nwords);

    return 0;
}

/* read all words 1 per-line, from 'fp', return
 * pointer-to-pointers of allocated strings on 
 * success, NULL otherwise, 'n' updated with 
 * number of words read.
 */
char **getwords (FILE *fp, int *n) {

    char **words = NULL;
    char buf[wordslength + 1] = {0};
    int maxlen = listlength > 0 ? listlength : 1;

    if (!(words = calloc (maxlen, sizeof *words))) {
        fprintf (stderr, "getwords() error: virtual memory exhausted.\n");
        return NULL;
    }

    while (fgets (buf, wordslength + 1, fp)) {

        size_t wordlen = strlen (buf);  /* get word length */

        if (buf[wordlen - 1] == '\n')   /* strip '\n' */
            buf[--wordlen] = 0;

        words[(*n)++] = strdup (buf);   /* allocate/copy */

        if (*n == maxlen) { /* realloc as required, update maxlen */
            void *tmp = realloc (words, maxlen * 2 * sizeof *words);
            if (!tmp) {
                fprintf (stderr, "getwords() realloc: memory exhausted.\n");
                return words; /* to return existing words before failure */
            }
            words = tmp;
            memset (words + maxlen, 0, maxlen * sizeof *words);
            maxlen *= 2;
        }
    }

    return words;
}

void free_array (char **words, int rows){

    int i;
    for (i = 0; i < rows; i++){
        free (words[i]);
    }
    free(words);
}

例子Use/Output

$ ./bin/dict ../dat/10int_nl.txt

 '10' words read from '../dat/10int_nl.txt'

8572
-2213
6434
16330
3034
12346
4855
16985
11250
1495

内存错误检查

在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 个责任:(1) 始终保留指向内存块起始地址的指针,因此,(2) 它可以是不再需要时释放。

您必须使用内存错误检查程序来确保您没有写入 beyond/outside 您分配的内存块,试图读取或基于未初始化的值进行跳转,并最终确认你已经释放了你分配的所有内存。

对于Linux valgrind是正常的选择。有许多微妙的方法可以滥用新的内存块。使用内存错误检查器可以让您识别任何问题并验证您分配的内存是否正确使用,而不是通过 segfault 发现问题存在。每个平台都有类似的内存检查器。它们都很简单易用,只需运行你的程序就可以了。

$ valgrind ./bin/dict ../dat/10int_nl.txt
==10212== Memcheck, a memory error detector
==10212== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==10212== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==10212== Command: ./bin/dict ../dat/10int_nl.txt
==10212==

 '10' words read from '../dat/10int_nl.txt'

8572
-2213
<snip>
11250
1495
==10212==
==10212== HEAP SUMMARY:
==10212==     in use at exit: 0 bytes in 0 blocks
==10212==   total heap usage: 15 allocs, 15 frees, 863 bytes allocated
==10212==
==10212== All heap blocks were freed -- no leaks are possible
==10212==
==10212== For counts of detected and suppressed errors, rerun with: -v
==10212== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

始终确认所有堆块都已释放——不可能有泄漏,同样重要的是错误摘要:0 个上下文中的 0 个错误

注意strdup

由于 strdup 分配内存(以及复制给定的字符串),您应该像检查 malloccalloc 一样检查 return 以保护防止内存耗尽。例如:

    if (!(words[(*n)++] = strdup (buf))) {
        fprintf (stderr, "getwords() error: virtual memory exhausted.\n");
        return NULL;
    }