使用 feof() 从文件读取并退出循环

Reading from file and exiting loop using feof()

提问人:Somjit 提问时间:12/14/2014 最后编辑:Somjit 更新时间:11/22/2018 访问量:1646

问:

这个链接讲述了为什么用作循环的退出指示符是一件坏事。feof()

不安全 ==> 在一段时间内检查,同时在里面检查。feof()fgets()

安全 ==> 在同时本身进行检查。fgets()!=NULL

我应该看到不安全的代码进行额外的 while 循环迭代,但两者都执行相同(且正确)的循环次数。有人可以帮我了解这里发生了什么吗?

编辑:该链接实际上确实说明了为什么会发生这种情况,但是我需要下面的正确答案才能准确理解我正在阅读的内容。我的文件在最后一行没有“\n”,所以得到了相同的结果。

这是文件内容:

abcd
efgh
ijkl

这是代码:

void testUnsafe(void) {
    FILE *f;
    char buf[20];
    f = fopen("fil.txt", "r");
    while (!feof(f)) {
        fgets(buf, 20, f);
        if (buf[strlen(buf) - 1] == '\n') //cleaner
            buf[strlen(buf) - 1] = '\0';
        printf("%s , %d\n", buf, strlen(buf));
    }
    fclose(f);
}

void testSafe(void) {
    FILE *f;
    char buf[20];
    f = fopen("fil.txt", "r");
    while (fgets(buf, 20, f) != NULL) {
        if (buf[strlen(buf) - 1] == '\n') //cleaner
            buf[strlen(buf) - 1] = '\0';
        printf("%s , %d\n", buf, strlen(buf));
    }
    fclose(f);
}

输出为:

******unsafe test********
abcd , 4
efgh , 4
ijkl , 4
********safe test********
abcd , 4
efgh , 4
ijkl , 4
C file-io fgets feof

评论

0赞 pmg 12/14/2014
尝试将文件放在 netwrok 驱动器上,在循环中进行某种暂停,并在读取文件时断开网线。
0赞 Somjit 12/14/2014
对不起,正如你所看到的,我不太擅长 C 来理解这里的问题,如果,也就是说,这不是一开始就讽刺的话。

答:

2赞 Weather Vane 12/14/2014 #1

我尝试了你的两个例子,得到了与你不同的结果。函数打印了我文件的最后一行两次。这有两个原因。testUnsafe()

  1. 如果读取操作尝试读取文件末尾,则该函数返回非零值。feof()

  2. 函数不检查 的返回值,因此在达到条件之前重复先前读取的字符串。testUnsafe()fgets()feof()

我将您的函数复制到我的测试程序中

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

void testUnsafe(void) {
    FILE *f;
    char buf[20];
    f = fopen("fil.txt", "r");
    while (!feof(f)) {
        fgets(buf, 20, f);
        if (buf[strlen(buf) - 1] == '\n') //cleaner
            buf[strlen(buf) - 1] = '\0';
        printf("%s , %d\n", buf, strlen(buf));
    }
    fclose(f);
}

void testSafe(void) {
    FILE *f;
    char buf[20];
    f = fopen("fil.txt", "r");
    while (fgets(buf, 20, f) != NULL) {
        if (buf[strlen(buf) - 1] == '\n') //cleaner
            buf[strlen(buf) - 1] = '\0';
        printf("%s , %d\n", buf, strlen(buf));
    }
    fclose(f);
}

int main()
{
    testUnsafe();
    printf ("\n\n");
    testSafe();
    return 0;
}

测试文件:

Line 1
Line 2
Line 3

输出:testUnsafe()

Line 1 , 6
Line 2 , 6
Line 3 , 6
Line 3 , 6

输出:testSafe()

Line 1 , 6
Line 2 , 6
Line 3 , 6

评论

0赞 Weather Vane 12/14/2014
根据@NisseEngström评论,这就是为什么它不安全。
0赞 Weather Vane 12/14/2014
最后一行之后没有,打印了正确的 3 行。newline
1赞 chux - Reinstate Monica 12/14/2014
在选定情况下(嵌入的 null 字符),为 0,因此为 UB。strlen(buf)if (buf[strlen(buf) - 1]
0赞 Somjit 12/14/2014
@chux :你能提供一个链接,让我可以(轻松)了解嵌入的空值吗?我的谷歌结果对我来说有点太难了。
0赞 Weather Vane 12/15/2014
@chux表示一行,例如 where 将读取并包括 ,但在读取它后,字符串函数将看到一个空字符串。我注意到了潜在的缺陷,但认为在这个例子中它不会发生,因为即使是一个空行也会有一个 ,如果最后一个文本行缺少一个,它必须至少有一个其他字符。但正如@chux所说,它可以。"\0some text\n"fgets()newlinenewlinenewline
3赞 Nisse Engström 12/14/2014 #2

如果文本文件在最后一行文本后没有换行符,则该函数将在读取最后一行时到达文件末尾,并生成您显示的三行输出。testUnsafe()

如果文本文件在最后一行文本后确实有换行符,则该函数将读取最后一行(包括换行符),而不会到达文件末尾。当它再次进入循环时,它会读取零个字符,设置文件结束标志,并输出最后一行,该行仍在上一轮的缓冲区中。while()

该建筑本身并非不安全。它忽略了检查它的返回值是不安全的。while (!feof(f))fgets()

评论

1赞 chux - Reinstate Monica 12/14/2014
关于“忽略检查不安全的 fgets() 的返回值”的好评论。
0赞 user10690348 11/22/2018 #3

基本上,要阅读所有行,您必须像这样使用算法。 使用文件末尾没有换行符的 ou,您一定会加载所有行。

这里的例外是最后一行不确定末尾有 LF。

除了检查缓冲区溢出之类的东西,为了优化内存使用,您还可以调用 realloc() 来修剪缓冲区,然后再将其添加到数组中。

buffer = (char*)malloc(bufferSize);
while(fgets(buffer, bufferSize, file) != NULL) {
    //here store your pointer in array...
    buffer = (char*)malloc(bufferSize);
};
free(buffer);