简单:这是未定义的行为,因为fflush是在输出流上调用的。这是C标准的摘录: int findDuplicate(); ostream指向输出流或更新流,其中最近的操作没有被输入,fflush函数使该流的任何未写入数据被传递到主机环境以写入文件;否则,行为是未定义的。 因此,这不是一个“有多糟糕”的问题。fflush(stdin)只是不可移植,所以如果希望代码在编译器之间可移植,就不应该使用它。
#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上,前四行看起来像:
##
# 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规范在它所说的内容上要谨慎得多-它指定了一个能够查找的文件,但是终端,当然,不支持查找。它也比微软规范少得多有用。
我相信你永远不应该调用fflush(stdin),原因很简单,你甚至不应该发现有必要在第一时间尝试刷新输入。现实地说,你可能认为你必须刷新输入的原因只有一个,那就是:跳过scanf卡住的一些坏输入。 例如,您可能有一个程序正在循环阅读使用scanf("%d", &n)的整数,很快您就会发现,当用户第一次键入非数字字符(如'x')时,the program goes into an infinite loop。 面对这种情况,我相信你基本上有三种选择: 1.以某种方式刷新输入(如果不使用fflush(stdin),则在循环中调用getchar以读取\n之前的字符,这是通常推荐的)。 1.告知用户不要在需要数字时键入非数字字符。
Use something other than scanf to read input的一个。 现在,如果你是一个初学者,scanf似乎是最简单的读取输入的方法,所以选择#3看起来很可怕也很困难。但是#2似乎是一个真实的的逃避,因为每个人都知道不友好的计算机程序是一个问题,所以做得更好会更好。所以太多的初学者被逼到了角落。感觉他们别无选择,只能做#1。他们或多或少必须使用scanf进行输入,这意味着它会在错误的输入上卡住,这意味着他们必须找到一种方法来刷新错误的输入,这意味着他们非常想使用fflush(stdin)。 我想鼓励所有初学C语言的程序员做出不同的权衡: 1.在你C语言编程生涯的早期阶段,在你习惯使用scanf以外的任何东西之前,只要 * 不要担心错误的输入 *。真的。继续使用上面的第二个借口吧。这样想:你是一个初学者,有很多事情你还不知道如何去做,其中一件你还不知道如何去做的事情是:优雅地处理意外输入。
learn how to do input using functions other than scanf。到那时,您就可以开始优雅地处理错误的输入了,而且您可以使用更多更好的技术,根本不需要尝试“清 debugging 误的输入”。 或者,换句话说,那些还在使用scanf的初学者应该放心地使用cop-out #2,当他们准备好的时候,他们应该从那里升级到技术#3,没有人应该使用技术#1来尝试刷新输入--当然不是使用fflush(stdin)。
使用fflush(stdin)刷新输入有点像dowsing for water使用一个形状像字母“S”的棍子。 帮助人们以某种“更好”的方式刷新输入就像冲到一个S形的探测器前说:“不,不,你做错了,你需要使用Y形的探测器!” 换句话说,真实的问题不是fflush(stdin)不工作。调用fflush(stdin)是一个潜在问题的症状。为什么你必须“刷新”输入?这是你的问题。 而且,通常情况下,潜在的问题是,您正在使用scanf,在其许多无用的模式之一,意外地在输入中留下换行符或其他“不需要的”文本。因此,最好的长期解决方案是learn how to do input using better techniques than scanf,这样您就不必处理其未经处理的输入和其他特性。
7条答案
按热度按时间xvw2m8pv1#
简单:这是未定义的行为,因为
fflush
是在输出流上调用的。这是C标准的摘录:int findDuplicate();
ostream指向输出流或更新流,其中最近的操作没有被输入,fflush函数使该流的任何未写入数据被传递到主机环境以写入文件;否则,行为是未定义的。
因此,这不是一个“有多糟糕”的问题。
fflush(stdin)
只是不可移植,所以如果希望代码在编译器之间可移植,就不应该使用它。flvlnr442#
TL;DR -* 可移植代码不使用
fflush(stdin)
*这个答案的其余部分解释了为什么可移植代码不使用
fflush(stdin)
。这很容易加上“可靠的代码不使用fflush(stdin)
“,这通常也是正确的。标准C和POSIX将
fflush(stdin)
保留为未定义行为fflush()
的POSIX、C和C++标准明确声明行为是未定义的(因为stdin
是输入流),但它们都不阻止系统定义它。ISO/IEC 9899:2011 -C11标准-说明:
§7.21.5.2 fflush函数
如果
stream
指向一个输出流或一个更新流,其中最近的操作没有被输入,fflush
函数会将该流的任何未写数据发送到主机环境,以写入文件;否则,行为是未定义的。POSIX主要遵循C标准,但它确实将此文本标记为C扩展。
[CX]对于一个打开阅读的流,如果该文件还没有被打开,并且该文件是一个能够查找的文件,则底层打开文件描述的文件偏移量应被设置为该流的文件位置,并且由
ungetc()
或ungetwc()
推回到流上的、随后未从流中读取的任何字符都将被丢弃(不进一步更改文件偏移量)。请注意,终端不能查找;管道和套接字也不能查找。
Microsoft将
fflush(stdin)
的行为定义为no-op在2015年,Microsoft和Visual Studio运行时用于定义
fflush()
在输入流上的行为,如下所示(但链接导致2021年的文本不同-甚至“2015”版本中的文本也不同):如果流是开放的,
fflush
将清除缓冲区的内容。M.M注意事项:
Cygwin是一个相当常见的平台的例子,在这个平台上,
fflush(stdin)
不清除输入。这就是为什么我的评论的这个回答版本注意到“微软和Visual Studio运行时”-如果你使用非Microsoft C运行时库,你看到的行为取决于该库。
Weather Vane在对另一个问题的评论中向我指出,在2021年6月之前的某个时候,微软改变了对
fflush()
的描述,与2015年编写这个答案时最初指定的内容相比,它现在说:如果流是在读模式下打开的,或者流没有缓冲区,则对
fflush
的调用无效,所有缓冲区都将保留。对fflush
的调用将取消任何先前对ungetc
的调用对流的影响。Caveat Lector:最好不要在任何平台上依赖
fflush(stdin)
。Linux的文档和实践似乎是矛盾的
令人惊讶的是,Linux名义上也记录了
fflush(stdin)
的行为,甚至以同样的方式定义了它(奇迹中的奇迹)。对于输入流,
fflush()
将丢弃从基础文件中获取但尚未被应用程序使用的任何缓冲数据。在2021年,报价更改为:
对于输入流,
fflush()
会丢弃所有从底层文件中提取但尚未被应用程序使用的缓冲数据。流的打开状态不受影响。Linux上的
fflush(3)
的另一个来源也同意(给予或接受段落休息):对于与可搜索文件(例如磁盘文件,但不是管道或终端)相关联的输入流,
fflush()
将丢弃从底层文件中获取但尚未被应用程序使用的任何缓冲数据。这些都没有明确地解决POSIX规范关于
ungetc()
的问题。2021年zwol评论说Linux文档已经改进了,在我看来,仍然有改进的空间。
在2015年,我对Linux文档中说
fflush(stdin)
可以工作感到有点困惑和惊讶。尽管有这样的建议,但它通常在Linux上不工作。我刚刚检查了Ubuntu 14.04 LTS的文档;它说的是上面引用的内容,但根据经验,它不工作-至少当输入流是不可查找的设备时,如终端。demo-fflush.c
字符串
输出示例
型
这个输出是在Ubuntu 14.04 LTS和Mac OS X 10.11.2上获得的。据我所知,它与Linux手册上的内容相矛盾。如果
fflush(stdin)
操作有效,我将不得不键入一行新的文本来获取第二个getchar()
读取的信息。考虑到POSIX标准的内容,可能需要更好的演示,并且应该澄清Linux文档。
demo-fflush2.c
型
输出示例
请注意,
/etc/passwd
是一个可查找的文件。在Ubuntu上,第一行看起来像:型
在Mac OS X上,前四行看起来像:
型
换句话说,在Mac OS X
/etc/passwd
文件的顶部有注解。非注解行符合正常布局,因此root
条目为:型
Ubuntu 14.04 LTS:
型
Mac OS X 10.11.2:
型
Mac OS X的行为忽略了(或者至少看起来忽略了)
fflush(stdin)
(因此在这个问题上不遵循POSIX)。Linux的行为对应于文档中的POSIX行为,但是POSIX规范在它所说的内容上要谨慎得多-它指定了一个能够查找的文件,但是终端,当然,不支持查找。它也比微软规范少得多有用。摘要
微软记录了
fflush(stdin)
的行为,但这种行为在2015年至2021年之间发生了变化。显然,它在Windows平台上使用本机Windows编译器和C运行时支持库进行工作。尽管有相反的文档,但当标准输入是终端时,它不能在Linux上工作,但它似乎遵循POSIX规范,该规范措辞更为谨慎。根据C标准,
fflush(stdin)
的行为是未定义的。POSIX添加了限定符“除非输入文件是可搜索的”,而终端不是。行为与Microsoft的不一样。因此,* 可移植代码不使用
fflush(stdin)
*。绑定到Microsoft平台的代码可以使用它,并且它可以按预期工作,但要注意可移植性问题。POSIX方式从文件描述符中丢弃未读的终端输入
从终端文件描述符(与
stdin
这样的文件流相反)中丢弃未读信息的POSIX标准方法在How can I flush unread data from a tty input queue on a Unix system中说明。然而,这是在标准I/O库级别下操作的。ax6ht2ek3#
根据标准,
fflush
只能用于输出缓冲区,显然stdin
不是一个。然而,some标准C库提供了fflush(stdin)
作为扩展的使用。在这种情况下,你可以使用它,但它会影响可移植性,所以你将不再能够使用地球上任何符合标准的标准C库,并期望同样的结果。oxf4rvwz4#
我相信你永远不应该调用
fflush(stdin)
,原因很简单,你甚至不应该发现有必要在第一时间尝试刷新输入。现实地说,你可能认为你必须刷新输入的原因只有一个,那就是:跳过scanf
卡住的一些坏输入。例如,您可能有一个程序正在循环阅读使用
scanf("%d", &n)
的整数,很快您就会发现,当用户第一次键入非数字字符(如'x'
)时,the program goes into an infinite loop。面对这种情况,我相信你基本上有三种选择:
1.以某种方式刷新输入(如果不使用
fflush(stdin)
,则在循环中调用getchar
以读取\n
之前的字符,这是通常推荐的)。1.告知用户不要在需要数字时键入非数字字符。
scanf
to read input的一个。现在,如果你是一个初学者,
scanf
似乎是最简单的读取输入的方法,所以选择#3看起来很可怕也很困难。但是#2似乎是一个真实的的逃避,因为每个人都知道不友好的计算机程序是一个问题,所以做得更好会更好。所以太多的初学者被逼到了角落。感觉他们别无选择,只能做#1。他们或多或少必须使用scanf
进行输入,这意味着它会在错误的输入上卡住,这意味着他们必须找到一种方法来刷新错误的输入,这意味着他们非常想使用fflush(stdin)
。我想鼓励所有初学C语言的程序员做出不同的权衡:
1.在你C语言编程生涯的早期阶段,在你习惯使用
scanf
以外的任何东西之前,只要 * 不要担心错误的输入 *。真的。继续使用上面的第二个借口吧。这样想:你是一个初学者,有很多事情你还不知道如何去做,其中一件你还不知道如何去做的事情是:优雅地处理意外输入。scanf
。到那时,您就可以开始优雅地处理错误的输入了,而且您可以使用更多更好的技术,根本不需要尝试“清 debugging 误的输入”。或者,换句话说,那些还在使用
scanf
的初学者应该放心地使用cop-out #2,当他们准备好的时候,他们应该从那里升级到技术#3,没有人应该使用技术#1来尝试刷新输入--当然不是使用fflush(stdin)
。jv4diomz5#
使用
fflush(stdin)
刷新输入有点像dowsing for water使用一个形状像字母“S”的棍子。帮助人们以某种“更好”的方式刷新输入就像冲到一个S形的探测器前说:“不,不,你做错了,你需要使用Y形的探测器!”
换句话说,真实的问题不是
fflush(stdin)
不工作。调用fflush(stdin)
是一个潜在问题的症状。为什么你必须“刷新”输入?这是你的问题。而且,通常情况下,潜在的问题是,您正在使用
scanf
,在其许多无用的模式之一,意外地在输入中留下换行符或其他“不需要的”文本。因此,最好的长期解决方案是learn how to do input using better techniques thanscanf
,这样您就不必处理其未经处理的输入和其他特性。r7xajy2e6#
现有的答案都没有指出这个问题的一个关键方面。
如果你发现自己想要“清除输入缓冲区”,你可能正在编写一个命令行交互式程序,更准确地说,你想要的是“丢弃当前输入行中你还没有读过的字符”。
**这不是
fflush(stdin)
所做的。*支持在输入流上使用fflush
的C库,将其记录为不做任何事情, 或丢弃 * 已从底层文件读取但未传递给应用程序的缓冲数据 *。这很容易是 * 更多 * 或 * 更少 * 在很多情况下,它可能是偶然工作的,因为终端驱动程序(在默认模式下)每次一行地向命令行交互式程序提供输入。但是,当你试图从磁盘上的一个实际文件向你的程序提供输入时(也许是为了自动化测试),内核和C库将切换到以大的“块”缓冲数据(通常为4到8 kB),与行边界无关,你会想知道为什么你的程序处理文件的第一行,然后跳过几十行,在下面一些明显随机的行的中间拾取。或者,如果你决定测试你的程序在一个非常长的行输入的手,然后终端驱动程序将无法给予的程序一次整行和fflush(stdin)
不会跳过所有的。那么你应该怎么做呢?我更喜欢的方法是,如果你一次处理一行输入,那么 * 一次读取一整行 *。C库有专门的函数:
fgets
个(在C90中,完全可移植,但仍然使您可以分块处理很长的行)和getline
(特定于POSIX,但会为您管理一个malloc
艾德缓冲区,这样您就可以一次处理所有的长行,不管它们有多长)。通常会有一个直接的翻译,从处理“当前行”的代码直接从stdin翻译为处理包含“当前行”的字符串的代码。bqjvbblv7#
引用自POSIX:
对于打开用于阅读的流,如果文件还没有处于打开状态,并且文件是能够查找的文件,则底层打开文件描述的文件偏移应被设置为流的文件位置,并且由ungec()或ungetwc()推回到流上的任何随后未从流读取的字符应被丢弃(不进一步改变文件偏移)。
请注意,终端无法查找。