使用 fflush(stdin)
因此,在 Google 上快速搜索用于清除输入缓冲区的 fflush(stdin)
会发现许多网站警告不要使用它。然而,这正是我的计算机科学教授教授全班的方式。
使用 fflush(stdin) 有多糟糕?即使我的教授正在使用它并且它看起来效果完美,我真的应该放弃使用它吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
简单:这是未定义的行为,因为 fflush 是在输出流上调用的。这是 C 标准的摘录:
所以这不是“有多糟糕”的问题。
fflush(stdin)
根本不可移植,因此如果您希望代码在编译器之间可移植,则不应使用它。Simple: this is undefined behavior, since
fflush
is meant to be called on an output stream. This is an excerpt from the C standard:So it's not a question of "how bad" this is.
fflush(stdin)
is simply not portable, so you should not use it if you want your code to be portable between compilers.将评论转换为答案。
TL;DR — 可移植代码不使用
fflush(stdin)
这个答案的其余部分解释了为什么可移植代码不使用
fflush(stdin)
。人们很容易添加“可靠的代码不使用 fflush(stdin)”,这通常也是正确的。标准 C 和 POSIX 将
fflush(stdin)
保留为未定义行为POSIX、
fflush()
的 C 和 C++ 标准明确声明该行为未定义(因为stdin
是输入流),但它们都没有阻止系统定义它。ISO/IEC 9899:2011 — C11 标准 — 规定:
POSIX 主要遵循 C 标准,但它确实将此文本标记为 C 扩展。
请注意,终端无法进行搜索;管道或插座也不是。
Microsoft 将
fflush(stdin)
的行为定义为无操作在 2015 年,Microsoft 和 Visual Studio 运行时用于在这样的输入流上定义 fflush() 的行为(但该链接在 2021 年会导致不同的文本 - 并且甚至“2015”版本中的文本也不同):
MM注释:
这就是为什么我的评论的这个答案版本指出“Microsoft和Visual Studio运行时” - 如果您使用非 Microsoft C 运行时库,您看到的行为取决于该库。
Weather Vane 在对另一个问题的评论中向我指出,在 2021 年 6 月之前的某个时间,微软更改了其
fflush 的说明()
与 2015 年编写此答案时最初指定的内容相比。现在显示:警告Lector:最好不要依赖
fflush(stdin)
在任何平台上。Linux 文档和实践似乎相互矛盾
令人惊讶的是,Linux 名义上记录了 < code>fflush(stdin) 也是如此,甚至以相同的方式定义它(奇迹中的奇迹)。这句话是2015年的。
2021 年,报价更改为:
另一个来源
fflush(3)
< Linux 上的 /a> 表示同意(给予或保留段落分隔符):这些都没有明确解决 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 上,前 4 行如下所示:
换句话说,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 规范在其表述上要谨慎得多 — 它指定了能够查找的文件,但终端当然不支持查找。它的用处也远不如 Microsoft 规范。摘要
Microsoft 记录了 fflush(stdin) 的行为,但该行为在 2015 年到 2021 年间发生了变化。显然,它的工作原理与 Windows 平台上记录的一样,使用本机 Windows 编译器和 C 运行时支持库。
尽管文档与此相反,当标准输入是终端时,它在 Linux 上不起作用,但它似乎遵循措辞更为仔细的 POSIX 规范。根据 C 标准,
fflush(stdin)
的行为是未定义的。 POSIX 添加了限定符“除非输入文件是可查找的”,而终端则不是。该行为与微软的不一样。因此,可移植代码不使用
fflush(stdin)
。与 Microsoft 平台相关的代码可以使用它并且它可以按预期工作,但要注意可移植性问题。丢弃来自文件描述符的未读终端输入的 POSIX 方法 丢弃
来自终端文件描述符的未读信息的 POSIX 标准方法(与
stdin
等文件流相反)在 如何从a中刷新未读数据Unix 系统上的 tty 输入队列。然而,这是在标准 I/O 库级别以下运行的。Converting comments into an answer.
TL;DR — Portable code doesn't use
fflush(stdin)
The rest of this answer explains why portable code does not use
fflush(stdin)
. It is tempting to add "reliable code doesn't usefflush(stdin)
", which is also generally true.Standard C and POSIX leave
fflush(stdin)
as undefined behaviourThe POSIX, C and C++ standards for
fflush()
explicitly state that the behaviour is undefined (becausestdin
is an input stream), but none of them prevent a system from defining it.ISO/IEC 9899:2011 — the C11 Standard — says:
POSIX mostly defers to the C standard but it does mark this text as a C extension.
Note that terminals are not capable of seeking; neither are pipes or sockets.
Microsoft defines the behaviour of
fflush(stdin)
as a no-opIn 2015, Microsoft and the Visual Studio runtime used to define the behaviour of
fflush()
on an input stream like this (but the link leads to different text in 2021 — and even the text in the '2015' version is different):M.M notes:
This is why this answer version of my comment notes 'Microsoft and the Visual Studio runtime' — if you use a non-Microsoft C runtime library, the behaviour you see depends on that library.
Weather Vane pointed out to me in a comment to another question that, at some time before June 2021, Microsoft changed its description of
fflush()
compared with what was originally specified when this answer was written in 2015. It now says:Caveat Lector: it is probably best not to rely on
fflush(stdin)
on any platform.Linux documentation and practice seem to contradict each other
Surprisingly, Linux nominally documents the behaviour of
fflush(stdin)
too, and even defines it the same way (miracle of miracles). This quote is from 2015.In 2021, the quote changes to:
And another source for
fflush(3)
on Linux agrees (give or take paragraph breaks):Neither of these explicitly addresses the points made by the POSIX specification about
ungetc()
.In 2021, zwol commented that the Linux documentation has been improved.
It seems to me that there is still room for improvement.
In 2015, I was a bit puzzled and surprised at the Linux documentation saying that
fflush(stdin)
will work.Despite that suggestion, it most usually does not work on Linux. I just checked the documentation on Ubuntu 14.04 LTS; it says what is quoted above, but empirically, it does not work — at least when the input stream is a non-seekable device such as a terminal.
demo-fflush.c
Example output
This output was obtained on both Ubuntu 14.04 LTS and Mac OS X 10.11.2. To my understanding, it contradicts what the Linux manual says. If the
fflush(stdin)
operation worked, I would have to type a new line of text to get information for the secondgetchar()
to read.Given what the POSIX standard says, maybe a better demonstration is needed, and the Linux documentation should be clarified.
demo-fflush2.c
Example output
Note that
/etc/passwd
is a seekable file. On Ubuntu, the first line looks like:On Mac OS X, the first 4 lines look like:
In other words, there is commentary at the top of the Mac OS X
/etc/passwd
file. The non-comment lines conform to the normal layout, so theroot
entry is:Ubuntu 14.04 LTS:
Mac OS X 10.11.2:
The Mac OS X behaviour ignores (or at least seems to ignore) the
fflush(stdin)
(thus not following POSIX on this issue). The Linux behaviour corresponds to the documented POSIX behaviour, but the POSIX specification is far more careful in what it says — it specifies a file capable of seeking, but terminals, of course, do not support seeking. It is also much less useful than the Microsoft specification.Summary
Microsoft documents the behaviour of
fflush(stdin)
, but that behaviour has changed between 2015 and 2021. Apparently, it works as documented on the Windows platform, using the native Windows compiler and C runtime support libraries.Despite documentation to the contrary, it does not work on Linux when the standard input is a terminal, but it seems to follow the POSIX specification which is far more carefully worded. According to the C standard, the behaviour of
fflush(stdin)
is undefined. POSIX adds the qualifier 'unless the input file is seekable', which a terminal is not. The behaviour is not the same as Microsoft's.Consequently, portable code does not use
fflush(stdin)
. Code that is tied to Microsoft's platform may use it and it may work as expected, but beware of the portability issues.POSIX way to discard unread terminal input from a file descriptor
The POSIX standard way to discard unread information from a terminal file descriptor (as opposed to a file stream like
stdin
) is illustrated at How can I flush unread data from a tty input queue on a Unix system. However, that is operating below the standard I/O library level.根据标准,
fflush
只能与输出缓冲区一起使用,显然stdin
不是其中之一。但是,一些标准 C 库提供了 fflush( stdin) 作为扩展。在那种情况下你可以使用它,但它会影响可移植性,因此你将不再能够使用地球上任何符合标准的标准C库并期望得到相同的结果。According to the standard,
fflush
can only be used with output buffers, and obviouslystdin
isn't one. However, some standard C libraries provide the use offflush(stdin)
as an extension. In that case you can use it, but it will affect portability, so you will no longer be able to use any standards-compliant standard C library on earth and expect the same results.我相信您永远不应该调用 fflush(stdin) ,原因很简单,您甚至不应该发现有必要首先尝试刷新输入。实际上,您可能认为必须刷新输入的原因只有一个,那就是:为了克服
scanf
卡住的一些错误输入。例如,您可能有一个程序正在使用
scanf("%d", &n)
循环读取整数。很快您就会发现,当用户第一次输入非数字字符(例如'x'
)时,程序进入无限循环。面对这种情况,我相信您基本上有三种选择:
\n
,正如通常建议的那样)。scanf 以外的其他内容
读取输入。现在,如果您是初学者,
scanf
似乎是读取输入的最简单方法,因此选择 #3 看起来既可怕又困难。但#2 似乎是一个真正的逃避,因为每个人都知道用户不友好的计算机程序是一个问题,所以最好做得更好。因此,太多的新手程序员陷入了困境,觉得自己别无选择,只能做第一件事。他们或多或少必须使用 scanf 进行输入,这意味着它将卡在错误的输入上,这意味着他们必须找到一种方法来刷新错误的输入,这意味着他们非常受诱惑使用fflush(stdin)。我想鼓励所有初级 C 程序员做出不同的权衡:
在 C 编程职业生涯的最初阶段,在您习惯使用
scanf
以外的任何内容之前,只是不用担心错误的输入。真的。继续使用上面的逃避#2。这样想:你是一个初学者,有很多事情你还不知道如何做,而你还不知道如何做的事情之一就是:优雅地处理意外的输入。< /p>尽快了解如何使用
scanf
以外的函数进行输入。那时,您可以开始优雅地处理错误输入,并且您将拥有更多、更好的技术可用,而根本不需要尝试“刷新错误输入”。或者,换句话说,仍然坚持使用
scanf
的初学者应该随意使用逃避 #2,当他们准备好时,他们应该从那里升级到技术 #3,并且没有人应该这样做使用技术 #1 来尝试刷新输入——当然不是使用 fflush(stdin) 。I believe that you should never call
fflush(stdin)
, and for the simple reason that you should never even find it necessary to try to flush input in the first place. Realistically, there is only one reason you might think you had to flush input, and that is: to get past some bad input thatscanf
is stuck on.For example, you might have a program that is sitting in a loop reading integers using
scanf("%d", &n)
. Soon enough you'll discover that the first time the user types a non-digit character like'x'
, the program goes into an infinite loop.When faced with this situation, I believe you basically have three choices:
fflush(stdin)
, then by callinggetchar
in a loop to read characters until\n
, as is often recommended).scanf
to read input.Now, if you're a beginner,
scanf
seems like the easiest way to read input, and so choice #3 looks scary and difficult. But #2 seems like a real cop-out, because everyone knows that user-unfriendly computer programs are a problem, so it'd be nice to do better. So all too many beginning programmers get painted into a corner, feeling that they have no choice but to do #1. They more or less have to do input usingscanf
, meaning that it will get stuck on bad input, meaning that they have to figure out a way to flush the bad input, meaning that they're sorely tempted to usefflush(stdin)
.I would like to encourage all beginning C programmers out there to make a different set of tradeoffs:
During the earliest stages of your C programming career, before you're comfortable using anything other than
scanf
, just don't worry about bad input. Really. Go ahead and use cop-out #2 above. Think about it like this: You're a beginner, there are lots of things you don't know how to do yet, and one of the things you don't know how to do yet is: deal gracefully with unexpected input.As soon as you can, learn how to do input using functions other than
scanf
. At that point, you can start dealing gracefully with bad input, and you'll have many more, much better techniques available to you, that won't require trying to "flush the bad input" at all.Or, in other words, beginners who are still stuck using
scanf
should feel free to use cop-out #2, and when they're ready they should graduate from there to technique #3, and nobody should be using technique #1 to try to flush input at all -- and certainly not withfflush(stdin)
.使用
fflush(stdin)
刷新输入有点像探水 使用形状像字母“S”的棍子。帮助人们以某种“更好”的方式刷新输入有点像冲向 S 杆探光器并说“不,不,你做错了,
你需要使用 Y 形棒!”。
换句话说,真正的问题不是 fflush(stdin) 不起作用。调用 fflush(stdin) 是潜在问题的症状。为什么您必须“刷新”输入?这就是您的问题。
通常,该潜在问题是您正在使用
scanf<。 /code> 的许多无用模式之一会意外地在输入中留下换行符或其他“不需要的”文本,因此,最好的长期解决方案是 学习如何使用比
scanf
更好的技术进行输入,这样您就不会根本不必处理其未处理的输入和其他特性。Using
fflush(stdin)
to flush input is kind of like dowsing for water using a stick shaped like the letter "S".And helping people to flush input in some "better" way is kind of like rushing up to an S-stick dowser and saying "No, no, you're doing it wrong,
you need to use a Y-shaped stick!".
In other words, the real problem isn't that
fflush(stdin)
doesn't work. Callingfflush(stdin)
is a symptom of an underlying problem. Why are you having to "flush" input at all? That's your problem.And, usually, that underlying problem is that you're using
scanf
, in one of its many unhelpful modes that unexpectedly leaves newlines or other "unwanted" text on the input. The best long-term solution, therefore, is to learn how to do input using better techniques thanscanf
, so that you don't have to deal with its unhandled input and other idiosyncrasies at all.现有的答案都没有指出问题的关键方面。
如果您发现自己想要“清除输入缓冲区”,那么您可能正在编写一个命令行交互式程序,更准确的说法是您想要丢弃当前行输入中您尚未读取的字符。
这不是中间中进行拾取。或者,如果您决定在手写的很长的行上测试程序,那么终端驱动程序将无法立即为程序提供整行和 fflush(stdin ) 不会跳过所有内容。
fflush(stdin)
的作用。支持在输入流上使用fflush
的 C 库将其记录为什么都不做,或者丢弃从底层文件读取的缓冲数据,但未传递给应用程序。这很容易比当前行的其余部分输入更多或更少。在很多情况下,它可能会意外地工作,因为终端驱动程序(在默认模式下)一次一行地向命令行交互程序提供输入。然而,当您尝试从磁盘上的实际文件向程序提供输入时(可能是为了自动化测试),内核和 C 库将切换到以大“块”(通常为 4 到 8 kB)缓冲数据,而不会产生任何影响。与行边界的关系,您会想知道为什么您的程序要处理文件的第一行,然后跳过几十行并在下面一些明显随机的行的那么你应该做什么呢?我更喜欢的方法是,如果您一次处理一行输入,则一次读取整行。 C 库有专门用于此目的的函数:
fgets
(在 C90 中,因此完全可移植,但仍然使您可以分块处理很长的行)和getline
(POSIX 特定的,但会为您管理一个 malloc 缓冲区,这样您就可以一次处理长行,无论它们有多长)。通常存在从直接从 stdin 处理“当前行”的代码到处理包含“当前行”的字符串的代码的直接转换。None of the existing answers point out a key aspect of the issue.
If you find yourself wanting to "clear the input buffer", you're probably writing a command-line interactive program, and it would be more accurate to say that what you want is to discard characters from the current line of input that you haven't already read.
This is not what
fflush(stdin)
does. The C libraries that support usingfflush
on an input stream, document it as either doing nothing, or as discarding buffered data that has been read from the underlying file but not passed to the application. That can easily be either more or less input than the rest of the current line. It probably does work by accident in a lot of cases, because the terminal driver (in its default mode) supplies input to a command-line interactive program one line at a time. However, the moment you try to feed input to your program from an actual file on disk (perhaps for automated testing), the kernel and C library will switch over to buffering data in large "blocks" (often 4 to 8 kB) with no relationship to line boundaries, and you'll be wondering why your program is processing the first line of the file and then skipping several dozen lines and picking up in the middle of some apparently random line below. Or, if you decide to test your program on a very long line typed by hand, then the terminal driver won't be able to give the program the whole line at once andfflush(stdin)
won't skip all of it.So what should you do instead? The approach that I prefer is, if you're processing input one line at a time, then read an entire line all at once. The C library has functions specifically for this:
fgets
(in C90, so fully portable, but does still make you process very long lines in chunks) andgetline
(POSIX-specific, but will manage amalloc
ed buffer for you so you can process long lines all at once no matter how long they get). There's usually a direct translation from code that processes "the current line" directly from stdin to code that processes a string containing "the current line".引用自 POSIX:
请注意,终端无法进行搜索。
Quote from POSIX:
Note that terminal is not capable of seeking.