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

时间:2016-02-02 02:46:18

标签: c arrays file dynamic-memory-allocation

我的目标是阅读我的文本文件" dictionary.txt"逐行并将其中的所有单词保存到我的数组中,现在称为单词,并希望打印它以确保数组包含所有内容,但我不熟悉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);
}

2 个答案:

答案 0 :(得分:1)

好的,我认为不是列出这里的所有错误;),我将为您重写getwords函数,并希望在此过程中教您。请注意,我在这里做了一些假设。我假设文件每行有一个单词,最大长度是cols参数。首先,我将参数名称更改为maxWordLen而不是cols(这更清楚)和getwords to getWords(这是约定)。像这样制作功能签名:

char** getWords(int rows, int maxWordLen)

你可以直接摆脱这两行:

int i;
FILE *dictionary;

对于分配,您需要在每个字符串的末尾包含空字符的空格。

//   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);
}

最后两行很好,只需关​​闭文件并返回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
}

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

答案 1 :(得分:1)

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

您使用fgets有点尴尬。由于您已将wordslength定义为21,因此您可以简单地静态声明buf的缓冲区(比如wordslength + 1)与fgets一起使用,然后分配/复制到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);
}

示例使用/输出

$ ./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)它可以在释放时释放它不再需要了。

必须使用内存错误检查程序,以确保您没有在已分配的内存块之外/之外写入,尝试读取或基于未初始化的值跳转,最后确认您已释放所有内存你分配的记忆。

对于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一样检查返回,以防止内存耗尽。 e.g:

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