why multiple scanf statments can't execute, even after using fflush function? [duplicate]
Solution 1:
Simple: this is undefined behavior, since fflush
is meant to be called on an output stream. This is an excerpt from the C standard:
int fflush(FILE *ostream);
ostream points to an output stream or an update stream in which the most recent operation was not input, the fflush function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.
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.
Solution 2:
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 use fflush(stdin)
", which is also generally true.
Standard C and POSIX leave fflush(stdin)
as undefined behaviour
The POSIX, C and C++ standards for fflush()
explicitly state that the behaviour is undefined (because stdin
is an input stream), but none of them prevent a system from defining it.
ISO/IEC 9899:2011 — the C11 Standard — says:
§7.21.5.2 The fflush function
¶2 If
stream
points to an output stream or an update stream in which the most recent operation was not input, thefflush
function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.
POSIX mostly defers to the C standard but it does mark this text as a C extension.
[CX] ⌦ For a stream open for reading, if the file is not already at EOF, and the file is one capable of seeking, the file offset of the underlying open file description shall be set to the file position of the stream, and any characters pushed back onto the stream by
ungetc()
orungetwc()
that have not subsequently been read from the stream shall be discarded (without further changing the file offset). ⌫
Note that terminals are not capable of seeking; neither are pipes or sockets.
Microsoft defines the behaviour of fflush(stdin)
In 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):
If the stream is open for input,
fflush
clears the contents of the buffer.
M.M notes:
Cygwin is an example of a fairly common platform on which
fflush(stdin)
does not clear the input.
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:
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. A call tofflush
negates the effect of any prior call toungetc
for the stream.
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.
For input streams,
fflush()
discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application.
In 2021, the quote changes to:
For input streams,
fflush()
discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application. The open status of the stream is unaffected.
And another source for fflush(3)
on Linux agrees (give or take paragraph breaks):
For input streams associated with seekable files (e.g., disk files, but not pipes or terminals),
fflush()
discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application.
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
#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;
}
Example output
$ ./demo-fflush
Alliteration
Got A; enter some new data
Got l
$
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 second getchar()
to read.
Given what the POSIX standard says, maybe a better demonstration is needed, and the Linux documentation should be clarified.
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;
}
Example output
Note that /etc/passwd
is a seekable file. On Ubuntu, the first line looks like:
root:x:0:0:root:/root:/bin/bash
On Mac OS X, the first 4 lines look like:
##
# User Database
#
# Note that this file is consulted directly only when the system is running
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 the root
entry is:
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
$
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.