C99:fscanf()比fgetc()早设置eof是标准的吗?

C99:fscanf()比fgetc()早设置eof是标准的吗?,c,scanf,eof,C,Scanf,Eof,我尝试在64位Windows PC上使用VS2017(32位版本),在我看来,fscanf()在成功读取文件中的最后一项后立即设置eof标志。fscanf()读取与流相关的文件中的最后一项后,此循环立即终止: while(!feof(stream)) { fscanf(stream,"%s",buffer); printf("%s",buffer); } 我知道这是不安全的代码。。。我只是想了解这种行为。请原谅我;-) 在这里,流与一个普通文本文件相关,该文件包含“Hello

我尝试在64位Windows PC上使用VS2017(32位版本),在我看来,fscanf()在成功读取文件中的最后一项后立即设置eof标志。fscanf()读取与流相关的文件中的最后一项后,此循环立即终止:

while(!feof(stream))
{
    fscanf(stream,"%s",buffer);
    printf("%s",buffer);
}
我知道这是不安全的代码。。。我只是想了解这种行为。请原谅我;-)

在这里,流与一个普通文本文件相关,该文件包含“Hello World!”之类的字符串。该文件中的最后一个字符不是换行符

但是,处理完最后一个字符后,fgetc()尝试读取此循环中的另一个字符,这将导致c=0xff(EOF):


fscanf()和fgetc()的这种行为是标准化的、依赖于实现的还是其他的?我不是在问循环为什么终止或者为什么不终止我感兴趣的问题是,这是否是标准行为

两个循环都不正确:
feof(f)
仅在尝试读取文件结尾后设置。在代码中,不测试
fgetc()
是否返回
EOF
,也不测试
fscanf()
是否返回
0
EOF

实际上,
fscanf()
可以在流到达文件结尾时设置流的文件结尾条件,如果文件不包含尾随换行符,
fgets()
则不会在文件以换行符结尾时设置此条件
fgetc()
仅在返回
EOF
时设置条件

下面是说明此行为的代码的修改版本:

#include <stdio.h>

int main() {
    FILE *fp = stdin;
    char buf[100];
    char *p;
    int c, n, eof;

    for (;;) {
       c = fgetc(fp);
       eof = feof(fp);
       if (c == EOF) {
           printf("c=EOF, feof()=%d\n", eof);
           break;
       } else {
           printf("c=%d, feof()=%d\n", c, eof);
       }
    }

    rewind(fp); /* clears end-of-file and error indicators */
    for (;;) {
        n = fscanf(fp, "%99s", buf);
        eof = feof(fp);
        if (n == 1) {
            printf("fscanf() returned 1, buf=\"%s\", feof()=%d\n", buf, eof);
        } else {
            printf("fscanf() returned %d, feof()=%d\n", n, eof);
            break;
        }
    }

    rewind(fp); /* clears end-of-file and error indicators */
    for (;;) {
        p = fgets(buf, sizeof buf, fp);
        eof = feof(fp);
        if (p == buf) {
            printf("fgets() returned buf, buf=\"%s\", feof()=%d\n", buf, eof);
        } else
        if (p == NULL) {
            printf("fscanf() returned NULL, feof()=%d\n", eof);
            break;
        } else {
            printf("fscanf() returned %p, buf=%p, feof()=%d\n", (void*)p, (void*)buf, eof);
            break;
        }
    }
    return 0;
}
C标准根据对
fgetc
的单独调用指定流函数的行为,
fgetc
在文件末尾无法从流中读取字节时设置文件结尾条件


上面演示的行为符合标准,并说明测试
feof()
不是验证输入操作的好方法
feof()
在操作成功后可以返回非零,在操作失败前可以返回
0
feof()
is只能用于在输入操作失败后区分文件结尾和输入错误。很少有程序能做到这一点,因此
feof()
几乎从未被故意使用过,而且几乎总是表示编程错误。如需更多解释,请阅读以下内容:

根据我的经验,在使用
时,“eof”和“error”位的精确语义非常非常微妙,以至于通常不值得(甚至不可能)尝试准确理解它们的工作原理。(关于这个问题,虽然涉及C++,而不是C)< /P> 我想您知道这一点,但首先要了解的是,
feof()
的目的并不是预测下一次输入是否会到达文件末尾。其目的甚至不是说输入流“在”文件的末尾。思考
feof()

这就是为什么

但您要问的是,与
getc
/
fgetc
相比,
fscanf
何时到达文件末尾并设置eof位。使用
getc
fgetc
,很容易:他们尝试读取一个字符,要么得到一个,要么得不到(如果得不到,要么是因为他们碰到了文件末尾,要么是遇到了i/o错误)

但是使用
fscanf
则更为棘手,因为根据所解析的输入说明符,字符只有在适合输入说明符时才被接受。例如,
%s
说明符不仅在遇到文件结尾或出现错误时停止,而且在遇到空白字符时也停止。(这就是为什么人们在评论中问你的输入文件是否以换行符结尾。)

我尝试过这个程序

#include <stdio.h>

int main()
{
    char buffer[100];
    FILE *stream = stdin;

    while(!feof(stream)) {
        fscanf(stream,"%s",buffer);
        printf("%s\n",buffer);
    }
}
具体来说,这四行都以新行结尾。结果是,毫不奇怪

这个
是
A.
测试。
测试。
最后一行是重复的,因为当(!feof(stream))
编写
时(通常)会发生这种情况

但后来我在输入端试了一下

这是什么\n
是\n
a\n
测试。
最后一行没有换行符。这一次,输出是

这个
是
A.
测试。
这一次,最后一行没有重复。(输出仍然与输入不同,因为输出包含四个换行,而输入包含三个。)

我认为这两种情况之间的区别在于,在第一种情况下,当输入包含新行时,
fscanf
读取最后一行,读取最后一行,注意到它是空白,并返回,但它没有达到EOF,因此没有设置EOF位。在第二种情况下,在没有尾随换行符的情况下,
fscanf
在读取最后一行时命中文件末尾,并设置eof位,因此满足
while()
条件中的
feof()
,代码不会在循环中进行额外的行程,并且最后一行不会重复

如果查看
fscanf
的返回值,我们可以更清楚地看到发生了什么。我对循环进行了如下修改:

while(!feof(stream)) {
    int r = fscanf(stream,"%s",buffer);
    printf("fscanf returned %2d: %5s (eof: %d)\n", r, buffer, feof(stream));
}
现在,当我在以换行符结尾的文件上运行它时,输出是:

fscanf返回1:This(eof:0)
fscanf返回1:is(eof:0)
fscanf返回1:a(eof:0)
fscanf返回1:测试。(eof:0)
fscanf返回-1:测试。(eof:1)
我们可以清楚地看到,在第四次调用之后,
feof(stream)
还不是真的,这意味着
#include <stdio.h>

int main()
{
    char buffer[100];
    FILE *stream = stdin;

    while(!feof(stream)) {
        fscanf(stream,"%s",buffer);
        printf("%s\n",buffer);
    }
}
while(!feof(stream)) {
    int r = fscanf(stream,"%s",buffer);
    printf("fscanf returned %2d: %5s (eof: %d)\n", r, buffer, feof(stream));
}
while((r = fscanf(stream,"%s",buffer)) == 1) {
    printf("%s\n", buffer);
}