92

因此,快速谷歌搜索fflush(stdin)清除输入缓冲区会发现许多网站警告不要使用它。然而,这正是我的 CS 教授教全班的方式。

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

4

7 回答 7

85

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

int fflush(FILE *ostream);

ostream 指向未输入最近操作的输出流或更新流, fflush 函数会导致该流的任何未写入数据被传递到主机环境以写入文件;否则,行为未定义。

所以这不是“有多糟糕”的问题。fflush(stdin)根本不可移植,因此如果您希望代码在编译器之间可移植,则不应使用它。

于 2010-06-05T04:53:39.553 回答
48

将评论转换为答案。

TL;DR —不使用可移植代码fflush(stdin)

这个答案的其余部分解释了为什么可移植代码不使用fflush(stdin). 添加“可靠的代码不使用fflush(stdin)”是很诱人的,这通常也是正确的。

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

POSIX、C 和 C++ 标准fflush()明确声明行为未定义(因为stdin是输入流),但它们都没有阻止系统定义它。

ISO/IEC 9899:2011——C11 标准——说:

§7.21.5.2 fflush 函数

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

POSIX 主要遵循 C 标准,但它确实将此文本标记为 C 扩展。

[CX] ⌦ 对于一个打开读取的流,如果文件不在EOF,并且该文件是可以查找的,则将底层打开文件描述的文件偏移设置为流的文件位置,并且任何被推回流的字符ungetc()ungetwc()随后未被从流中读取的字符都应被丢弃(无需进一步更改文件偏移量)。⌫</p>

请注意,终端无法搜索;管道或套接字也不是。

微软定义的行为fflush(stdin)

在 2015 年,Microsoft和 Visual Studio 运行时用于定义这样fflush()的输入流上的行为(但链接在 2021 年导致不同的文本):

如果流对输入打开,则fflush清除缓冲区的内容。

MM 注意事项

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

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

Weather Vane在对另一个问题的评论中向我指出,在 2021 年 6 月之前的某个时间,fflush()与 2015 年编写此答案时最初指定的内容相比,微软更改了其描述。它现在说:

如果流以读取模式打开,或者流没有缓冲区,则调用fflush无效,并且保留任何缓冲区。对流的调用fflush否定任何先前调用ungetc对流的影响。

警告讲师:最好不要依赖fflush(stdin)任何平台。

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

令人惊讶的是,Linux名义上记录了fflush(stdin)too 的行为,甚至以相同的方式定义它(奇迹的奇迹)。此报价来自 2015 年。

对于输入流,fflush()丢弃任何已从底层文件获取但尚未被应用程序使用的缓冲数据。

2021 年,报价更改为:

对于输入流,fflush()丢弃任何已从底层文件获取但尚未被应用程序使用的缓冲数据。流的打开状态不受影响。

Linux 上的另一个来源也fflush(3)同意(给予或采取段落中断):

对于与可查找文件相关联的输入流(例如,磁盘文件,但不是管道或终端),fflush()丢弃任何已从底层文件获取但尚未被应用程序使用的缓冲数据。

这些都没有明确解决 POSIX 规范提出的关于ungetc().

2021 年,zwol 评论说 Linux 文档得到了改进。在我看来,还有改进的余地。

2015 年,我对 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 记录了fflush(stdin).

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

因此,可移植代码不使用fflush(stdin). 绑定到 Microsoft 平台的代码可以使用它并且可以按预期工作,但要注意可移植性问题。

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

How can I flush unread data from a tty input queue on a Unix systemstdin中说明了从终端文件描述符(与类似文件流相反)丢弃未读信息的 POSIX 标准方法。但是,这是在标准 I/O 库级别以下运行的。

于 2015-12-13T01:26:08.183 回答
22

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

于 2010-06-05T05:10:38.810 回答
13

我相信你永远不应该调用fflush(stdin),原因很简单,你甚至不应该发现有必要首先尝试刷新输入。实际上,您可能认为必须刷新输入的原因只有一个,那就是:克服一些scanf卡住的错误输入。

例如,您可能有一个程序正在使用scanf("%d", &n). 很快你就会发现当用户第一次输入一个非数字字符'x'时,程序进入了一个无限循环

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

  1. 以某种方式刷新输入(如果不使用fflush(stdin),则通过调用getchar循环来读取字符 until \n,这通常是推荐的)。
  2. 告诉用户在需要数字时不要输入非数字字符。
  3. 使用除scanf读取输入之外的其他内容。

现在,如果您是初学者,这scanf 似乎是读取输入的最简单方法,因此选择 #3 看起来既可怕又困难。但是#2似乎是一个真正的逃避,因为每个人都知道用户不友好的计算机程序是一个问题,所以做得更好会很好。因此,太多的初级程序员都陷入了困境,觉得他们别无选择,只能做#1。他们或多或少必须使用 进行输入scanf,这意味着它会卡在错误的输入上,这意味着他们必须想办法清除错误的输入,这意味着他们非常想使用fflush(stdin).

我想鼓励所有初级 C 程序员做出不同的权衡:

  1. 在您 C 编程生涯的最初阶段,在您习惯使用除 之外的任何东西之前scanf不要担心输入错误。真的。继续使用上面的 cop-out #2。可以这样想:你是一个初学者,有很多事情你还不知道怎么做,而你还不知道怎么做的一件事是:优雅地处理意外的输入。

  2. 尽快学习如何使用除scanf. 那时,您可以开始优雅地处理错误输入,并且您将拥有更多更好的技术可供您使用,根本不需要尝试“清除错误输入”。

或者,换句话说,仍然坚持使用的初学者scanf应该随意使用 cop-out #2,当他们准备好时,他们应该从那里毕业到技术 #3,并且没有人应该使用技术 #1 来尝试完全刷新输入 - 当然不是fflush(stdin).

于 2019-11-15T20:18:40.607 回答
7

fflush(stdin)用于冲洗输入有点像使用形状像字母“S”的棍子对水进行探查。

帮助人们以某种​​“更好”的方式刷新输入有点像冲上 S 型探矿器并说“不,不,你做错了,你需要使用 Y 形探棒!”。

换句话说,真正的问题不在于fflush(stdin)它不起作用。呼叫fflush(stdin)是潜在问题的症状。为什么你必须“刷新”输入? 那是你的问题。

而且,通常,潜在的问题是您正在使用scanf, 在其许多无用的模式中,意外地在输入中留下换行符或其他“不需要的”文本。因此,最好的长期解决方案是学习如何使用比 更好的技术进行输入scanf,这样您就不必处理其未处理的输入和其他特性。

于 2020-04-24T12:46:12.840 回答
3

现有的答案都没有指出问题的关键方面。

如果您发现自己想要“清除输入缓冲区”,那么您可能正在编写一个命令行交互式程序,更准确的说法是您想要丢弃当前输入中的字符'还没读过。

这不是什么fflush(stdin)fflush支持在输入流上 使用的 C 库将其记录为不执行任何操作或丢弃已从底层文件读取但未传递给应用程序的缓冲数据很容易或多或少输入比当前行的其余部分。在很多情况下,它可能会意外工作,因为终端驱动程序(在其默认模式下)一次向命令行交互式程序提供一行输入。但是,当您尝试从磁盘上的实际文件(可能用于自动测试)向程序提供输入时,内核和 C 库将切换到以大“块”(通常 4 到 8 kB)缓冲数据,而没有与行边界的关系,您会想知道为什么您的程序正在处理文件的第一行,然后跳过几十行并在下面一些明显随机的行中间拾取。或者,如果您决定长时间测试您的程序手动键入的行,那么终端驱动程序将无法一次为程序提供整行,fflush(stdin)也不会跳过所有行。

那么你应该怎么做呢?我更喜欢的方法是,如果您一次处理一行输入,则一次读取一整行。C 库具有专门用于此的功能:(fgets在 C90 中,完全可移植,但仍然让您处理非常长的块)和getline(POSIX 特定,但将为malloc您管理一个 ed 缓冲区,因此您可以处理所有长行一次,无论他们得到多长时间)。通常从处理“当前行”的代码直接从标准输入直接转换为处理包含“当前行”的字符串的代码。

于 2021-06-09T14:54:10.787 回答
2

来自POSIX的报价:

对于一个打开读取的流,如果文件不在EOF,并且文件是可以查找的,则底层打开文件描述的文件偏移量应设置为流的文件位置,并且任何字符被推回通过 ungetc() 或 ungewc() 将其上传到流中,随后未从流中读取的将被丢弃(无需进一步更改文件偏移量)。

请注意,终端无法搜索。

于 2018-05-18T03:08:36.330 回答