使用fflush(stdin)


77

因此,通过Google进行快速搜索以fflush(stdin)清除输入缓冲区,就会发现许多网站警告不要使用它。但这正是我的CS教授教课的方式。

使用有多糟糕fflush(stdin)?即使我的教授正在使用它,并且它似乎可以正常工作,我还是应该放弃使用它吗?



8
双方的WindowsLinux的定义的行为fflush()上的输入流,甚至定义它以同样的方式(奇迹中的奇迹)。用于POSIX,C和C ++的标准fflush()没有定义行为,但是没有一个阻止系统定义行为。如果您要编码以获得最大的可移植性,请避免fflush(stdin); 如果您要为定义行为的平台进行编码,请使用它-但要注意它不是可移植的。
Jonathan Leffler

Cygwin是一个相当常见的平台示例,在该平台上fflush(stdin);不会清除输入。
MM

2
这也完全取决于您的期望fflush(stdin)
基思·汤普森

@JonathanLeffler Windows文档说If the stream was opened in read mode, or if the stream has no buffer, the call to fflush has no effect, and any buffer is retained,而Linux文档说For input streams, fflush() discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application.的方式并不完全相同,Windows保留缓冲区,而Linux丢弃缓冲区。
ssbssa

Answers:


76

简单:这是未定义的行为,因为fflush它是在输出流上调用的。这是C标准的摘录:

int fflush(FILE * ostream);

ostream指向未输入最新操作的输出流或更新流,fflush函数使该流的所有未写入数据都将被传递到主机环境中,并被写入文件中;否则,行为是不确定的。

因此,这不是“有多严重”的问题。fflush(stdin)完全错误的,你不能使用它,永远


16
@BlueRaja:这里有一个针对新手错误的辩护,但没有一个针对传播错误知识的老师的辩护fflush在第一段中,任何对的引用都清楚地表明它是针对输出流的,您不必为此记住C标准!
Eli Bendersky 2010年

6
@Eli:没人能知道所有的事情。在有人告诉他之前,处理器永远不会知道他的错误...我用fflush(stdin)了好多年,直到我发现它是UB(偶然)
BlueRaja-Danny Pflughoeft 2010年

4
错误,在使用某个功能之前,通常不应该查阅该文档吗?尤其是教授?
Alex Budovski

6
另一道防线是手册页的以下部分(Linux上的各种glibc版本):“对于输入流,fflush()丢弃已从基础文件中获取但未被应用程序使用的所有缓冲数据。流的状态不受影响。” 尽管它是UB,但某些实现似乎在不提及其相对于标准的地位的情况下做出了保证。
丹尼尔·菲舍尔

4
我很少看到另一个方面:fflush(stdin)比仅实现定义的行为差很多。即使它确实按照大多数人的意愿工作,也将是可怕的。想象一下,如果stdin不是一个笨拙地输入内容的人,而是来自另一个程序或shell重定向:它会读取文件的开头,然后仅删除其余部分。认为stdin总是像人类操作员一样慢,真是愚蠢。
拉斐尔·莱尔姆

41

将注释转换为答案-并扩展它们,因为问题会定期出现。

标准C和POSIX保留fflush(stdin)为未定义行为

POSIX,C和C ++标准fflush()明确说明该行为是未定义的,但它们都没有防止系统定义它。

ISO / IEC 9899:2011(C11标准)说:

§7.21.5.2冲洗功能

¶2如果stream指向未输入最新操作的输出流或更新流,则该fflush函数会使该流的任何未写入数据被传递到主机环境中,并被写入文件中;否则,行为是不确定的。

POSIX大多遵循C标准,但确实将此文本标记为C扩展。

[CX]对于打开供阅读的流,如果文件尚未在EOF上,并且该文件是能够搜索的文件,则基础打开文件描述的文件偏移应设置为该流的文件位置,并且任何由流推回流中的字符ungetc()ungetwc()随后未从流中读取的字符将被丢弃(无需进一步更改文件偏移)。

请注意,终端无法搜索;都不是管道或插座。

Microsoft定义了以下行为 fflush(stdin)

Microsoft和Visual Studio运行时定义fflush()输入流上行为的定义。

如果打开了流供输入,请fflush清除缓冲区的内容。

MM 备注

Cygwin是一个相当常见的平台示例,在该平台上fflush(stdin)不会清除输入。

这就是为什么我的评论的此答案版本注释为“ Microsoft和Visual Studio运行时”-如果您使用非Microsoft C运行时库,则看到的行为取决于该库。

Linux文档和实践似乎相互矛盾

出乎意料的是,Linux名义上也记录了行为fflush(stdin),甚至以相同的方式定义了行为(奇迹)。

对于输入流,fflush()将丢弃所有已从基础文件中提取但尚未被应用程序使用的缓冲数据。

我对Linux文档说仍然有效感到有些困惑和惊讶fflush(stdin)。尽管有这样的建议,但它通常在Linux上不起作用。我刚刚查看了Ubuntu 14.04 LTS上的文档;它说了上面引用的内容,但是从经验上讲,它是行不通的-至少当输入流是诸如终端之类的不可搜索的设备时。

demo-fflush.c

#include <stdio.h>

int main(void)
{
    int c;
    if ((c = getchar()) != EOF)
    {
        printf("Got %c; enter some new data\n", c);
        fflush(stdin);
    }
    if ((c = getchar()) != EOF)
        printf("Got %c\n", c);

    return 0;
}

输出示例

$ ./demo-fflush
Alliteration
Got A; enter some new data
Got l
$

此输出是在Ubuntu 14.04 LTS和Mac OS X 10.11.2上获得的。据我了解,它与Linux手册中所说的相矛盾。如果该fflush(stdin)操作可行,则必须键入新的一行文本以获取信息以供秒getchar()读。

根据POSIX标准所说,也许需要更好的演示,并且应该澄清Linux文档。

demo-fflush2.c

#include <stdio.h>

int main(void)
{
    int c;
    if ((c = getchar()) != EOF)
    {
        printf("Got %c\n", c);
        ungetc('B', stdin);
        ungetc('Z', stdin);
        if ((c = getchar()) == EOF)
        {
            fprintf(stderr, "Huh?!\n");
            return 1;
        }
        printf("Got %c after ungetc()\n", c);
        fflush(stdin);
    }
    if ((c = getchar()) != EOF)
        printf("Got %c\n", c);

    return 0;
}

输出示例

请注意,这/etc/passwd是一个可搜索的文件。在Ubuntu上,第一行如下所示:

root:x:0:0:root:/root:/bin/bash

在Mac OS X上,前4行如下所示:

##
# User Database
# 
# Note that this file is consulted directly only when the system is running

换句话说,Mac OS X/etc/passwd文件的顶部有注释。非注释行符合常规布局,因此root输入为:

root:*:0:0:System Administrator:/var/root:/bin/sh

Ubuntu 14.04 LTS:

$ ./demo-fflush2 < /etc/passwd
Got r
Got Z after ungetc()
Got o
$ ./demo-fflush2
Allotrope
Got A
Got Z after ungetc()
Got B
$

Mac OS X 10.11.2:

$ ./demo-fflush2 < /etc/passwd
Got #
Got Z after ungetc()
Got B
$

Mac OS X行为会忽略(或至少似乎忽略)fflush(stdin)(因此在此问题上未遵循POSIX)。Linux行为与已记录的POSIX行为相对应,但是POSIX规范说得更谨慎-它指定了能够搜索的文件,但是终端当然不支持搜索。它也没有Microsoft规范有用。

概要

Microsoft记录了以下行为 fflush(stdin)。显然,它使用本机Windows编译器和C运行时支持库,按Windows平台上的说明工作。

尽管有相反的说明,但是当标准输入是终端时,它在Linux上不起作用,但是它似乎遵循POSIX规范,而该规范的措辞要更为谨慎。根据C标准,行为fflush(stdin)不确定。POSIX添加了限定符“除非可以查找输入文件”,而终端则不行。行为与Microsoft的行为不同。

因此,可移植代码不使用fflush(stdin)。与Microsoft平台绑定的代码可以使用它并且可以运行,但是请注意可移植性问题。

POSIX方法丢弃来自文件描述符的未读终端输入

如何在Unix系统上从tty输入队列清除未读数据的POSIX标准方法,用于从终端文件描述符中丢弃未读信息(与之类似的文件流stdin)。但是,该操作低于标准I / O库级别。


就OP的要求而言,这可能是更好的答案,尽管公认的答案没有错。为了清楚地表明它一方面不符合标准,但另一方面也表明它可能正确地用于特定的实现。+1
RobertS

21

根据标准,fflush只能与输出缓冲区一起使用,显然stdin不是一个。但是,某些标准C库提供fflush(stdin)了扩展的使用。在那种情况下,您可以使用它,但是它将影响可移植性,因此您将不再能够使用地球上任何符合标准的标准C库,并且期望得到相同的结果。


8

我相信您永远不应该致电fflush(stdin),原因很简单,您甚至根本不需要尝试刷新输入。实际上,您可能以为只有一个原因,那就是:克服一些scanf卡住的错误输入。

例如,您可能有一个程序正坐在循环中,使用读取整数scanf("%d", &n),并且您发现用户第一次键入非数字字符(如)时'x'该程序会进入无限循环

面对这种情况,我相信您基本上有三个选择:

  1. 以某种方式刷新输入(如果不使用fflush(stdin),则通过调用getchar读取字符直到\n通常建议。
  2. 告诉用户不要在需要数字时键入非数字字符。
  3. 使用除scanf读取输入以外的内容

现在,如果您是初学者,scanf 似乎是读取输入内容的最简单方法,因此选择#3既麻烦又困难。但是#2似乎是一个真正的解决方案,因为每个人都知道用户不友好的计算机程序是个问题,因此最好做得更好。因此,太多的新手程序员陷入了困境,觉得他们别无选择,只能做#1。他们或多或少必须使用进行输入scanf,这意味着它将卡在错误的输入上,这意味着他们必须找出一种方法来清除错误的输入,这意味着他们非常想使用fflush(stdin)

我想鼓励所有新手程序员进行一系列不同的权衡:

  1. 在您的C编程职业生涯的最初阶段,在您习惯使用其他任何东西之前scanf请不要担心输入错误。真。继续并使用上面的#2。这样思考:您是一个初学者,很多事情您尚不知道该怎么做,而您尚不知道如何做的一件事是:应对意外的输入。

  2. 请尽快学习如何使用以外的功能进行输入scanf。到那时,您可以开始优雅地处理错误的输入,并且您将获得更多,更好的技术,根本不需要尝试清除错误的输入。

或者,换句话说,仍然坚持使用的初学者scanf应该随意使用cop-out#2,当他们准备就绪时,他们应该从那里毕业到#3,而没有人应该使用#1来尝试。完全清空输入(当然不能使用fflush(stdin)


关于nitpick的一点是,因为我认为它有点模棱两可,并且有人可能会误解您:“以某种方式刷新输入(如果不使用fflush(stdin),则按通常的建议通过调用getchar读取字符直到\n,通常是这样。)。 ”-对getchar()do的调用在找到字符之前,不要阅读字符s\n。如果有多个字符,对的一次调用getchar()将仅获取输入的最后一个字符,而不是所有字符(包括换行符)。此外,getchar()还可以消耗换行符。
RobertS

2

使用fflush(stdin)同花顺的输入是一种像占卜杖探测水使用形如字母“S”棍子。

帮助人们以某种​​“更好”的方式冲洗输入,就像冲上S型棍的推土铲,然后说:“不,不,您做错了,您需要使用Y形棍!”。

换句话说,真正的问题不是那fflush(stdin)不起作用。呼叫fflush(stdin)是潜在问题的征兆。为什么根本要“刷新”输入? 那是你的问题。

而且,通常情况下,潜在的问题是您正在使用scanf,它处于许多令人困惑的模式之一,这种模式出乎意料地在输入上留下了换行符或其他空格。因此,最佳的长期答案是学习如何使用比更好的技术进行输入scanf


虽然我猜您忘了这个答案stackoverflow.com/a/58884121/918959
Antti Haapala

@AnttiHaapala感谢您的指导,但是,不,我没有忘记;这两个答案都链接在我关于该主题的注释中。在stackoverflow.com/questions/34219549上还有更多不错的规范答案。
史蒂夫·萨米特

我的意思是,他们在同一个问题上:D
Antti Haapala

@AnttiHaapala是的,我明白了。当我发布第二篇文章时,SO问我“您已经对这个问题有了答案,确定要再次回答吗?”,然后我回答,“是”。对于这些永恒的问题,我一直在尝试寻找不同/更好/替代的答案方式。(又如stackoverflow.com/questions/949433。)
史蒂夫峰会

1

引用POSIX

对于打开供阅读的流,如果该文件尚未在EOF上,并且该文件具有查找能力,则应将基础打开文件描述的文件偏移量设置为该流的文件位置,并推回任何字符由ungetc()或ungetwc()写入流中的流,这些流随后将不再从流中读取(无需进一步更改文件偏移)。

请注意,终端无法搜索。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.