我有一个大约150000字的记事本文件(代表字典)。我试着扫描每个单词并打印到控制台上。此设置工作正常:

void readDictionary(FILE *ifp, int numWords) {
    fscanf(ifp, "%d", &numWords);
    printf("%d\n", numWords);

    int i;
    char* words = (char*)malloc(20 * sizeof(char));
    for(i = 0; i < numWords; i++) {
        fscanf(ifp, "%s", words);
        printf("%s\n", words);
    }
}

但是,这段代码显然在每次循环时都会覆盖“words”。我试图将每个单词保存到某个数组元素中。我做了以下操作,但它立即崩溃(我将内存分配更改为2D,因为我在这里阅读,这似乎是我应该做的事情):
void readDictionary(FILE *ifp, int numWords) {
    fscanf(ifp, "%d", &numWords);
    printf("%d\n", numWords);

    int i;
    char** words = (char**)malloc(20 * sizeof(char*));
    for(i = 0; i < numWords; i++) {
        fscanf(ifp, "%s", words[i]);
        printf("%s\n", words[i]);
    }
}

如有任何帮助,我们将不胜感激。我在很多帖子上都读过,但还没搞清楚。

最佳答案

第一个问题是您只为单词列表(即字符指针)分配了空间,但没有为单词本身分配空间。

char** words = (char**)malloc(20 * sizeof(char*));

这将为20个字符的指针分配空间并将其分配给words。现在words[i]有一个字符指针的空间,但没有字符指针的空间。
words[i]包含垃圾,因为malloc不初始化内存。当您将其传递到fscanf时,fscanf会尝试使用words[i]中的垃圾作为向其写入字符的内存位置。这要么会损坏程序中的某些内存,要么更可能it tries to read a memory location is isn't allowed to and crashes。不管怎样,都不好。
必须为字符串分配内存,将其传递给fscanf,最后将该字符串放入words[i]
char** words = malloc(numWords * sizeof(char*));
for(i = 0; i < numWords; i++) {
    char *word = malloc(40 * sizeof(char));
    fscanf(ifp, "%39s", word);
    words[i] = word;
    printf("%s\n", words[i]);
}

请注意,我没有投射mallocthat's generally considered unnecessary的结果。
还要注意,我在列表中为numWords分配了空间。你的原始文件只分配20个字的空间,一旦超过这个空间,它就会开始覆盖分配的内存,可能会崩溃。作为一个经验法则,避免恒定的内存分配。尽快适应动态内存分配。
还要注意,我限制了可以读取缓冲区大小的fscanf字符数(由于字符串末尾的空字节而减少了一个)。否则,如果您的单词列表包含45个字符的“肺炎-结核-矽肺-嗅觉锥虫病”,它将超出word缓冲区并开始在相邻元素上乱涂乱画,这将是不好的。
这将导致fscanfscanf常见的新问题:部分读取。当上述代码遇到“肺炎支原体矽肺孢子虫病”fscanf(ifp, "%39s", word);时,将在前39个字符中读取“肺炎支原体矽肺孢子虫病”并停止。下一次调用fscanf时将显示“niosis”。你会把它们当作两个字来储存和打印。那不好。
你可以通过增大单词缓冲区来解决这个问题,但是现在大多数单词将浪费大量内存。
scanf and fscanf have a whole lot of problems and are best avoided。相反,最好阅读整行并用sscanf解析它们。在这种情况下,您不需要进行任何解析,它们只是字符串,因此获取行就足够了。
fgets是阅读一行的常用方法,但这也要求您尝试猜测需要在该行中读取多少内存。为了减轻这种情况,需要一个大的行缓冲区,并从中复制单词。
void strip_newline( char* string ) {
    size_t len = strlen(string);
    if( string[len-1] == '\n' ) {
        string[len-1] = '\0';
    }
}

...

int i;

/* The word list */
char** words = malloc(numWords * sizeof(char*));

/* The line buffer */
char *line = malloc(1024 * sizeof(char*));

for(i = 0; i < numWords; i++) {
    /* Read into the line buffer */
    fgets(line, 1024, ifp);

    /* Strip the newline off, fgets() doesn't do that */
    strip_newline(line);

    /* Copy the line into words */
    words[i] = strdup(line);

    printf("%s\n", words[i]);
}

strdup不会复制所有1024个字节,只复制足够的字。这将导致只使用所需的内存。
假设文件有一定数量的行,这会导致问题。即使文件说它包含一定数量的行,您仍然应该验证这一点。否则,当您试图读取超过文件结尾的内容时,会出现奇怪的错误。在这种情况下,如果文件小于numWords,它将尝试读取垃圾并可能崩溃。相反,您应该读取文件,直到没有更多的行。
通常这是通过在while循环中检查fgets的返回值来完成的。
int i;
for( i = 0; fgets(line, 1024, ifp) != NULL; i++ ) {
    strip_newline(line);
    words[i] = strdup(line);
    printf("%s\n", words[i]);
}

这就带来了一个新问题,我们如何知道要制造多大的words?你不会的。这会让我们增长和重新分配记忆。这个答案太长了,所以我就画个草图。
char **readDictionary(FILE *ifp) {
    /* Allocate a decent initial size for the list */
    size_t list_size = 256;
    char** words = malloc(list_size * sizeof(char*));

    char *line = malloc(1024 * sizeof(char*));

    size_t i;
    for( i = 0; fgets(line, 1024, ifp) != NULL; i++ ) {
        strip_newline(line);

        /* If we're about to overflow the list, double its size */
        if( i > list_size - 1 ) {
            list_size *= 2;
            words = realloc( words, list_size * sizeof(char*));
        }

        words[i] = strdup(line);
    }

    /* Null terminate the list so readers know when to stop */
    words[i] = NULL;

    return words;
}

int main() {
    FILE *fp = fopen("/usr/share/dict/words", "r");
    char **words = readDictionary(fp);

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

现在列表将从256开始,并根据需要增长。双倍增长非常快,不会浪费太多的记忆。My/usr/share/dict/words中有235886行。可以存储在218或262144中。256是28,因此它只需要10个昂贵的调用就可以将realloc扩展到所需的大小。
我把它改成了返回列表,因为如果你想立即使用它,那么构建列表就没什么好处了。这允许我演示使用动态大小的列表的另一种技术,空终止。列表中的最后一个元素被设置为NULL,因此任何阅读列表的人都知道何时停止。这比用列表传递长度更安全和简单。
这是很多,但这是在C语言中处理文件时需要做的所有基本工作。手动操作是很好的,但幸运的是,有一些库可以使这类操作变得容易得多。例如,Gnome Lib provides a lot of basic functionality包括arrays of pointers that automatically grow as needed

10-08 07:36
查看更多