scanf and other fun
Can you spot the problem with the following code?
int val; printf("Enter a value: "); scanf("%d", &val);
I don't think there's any insight that will come from staring at it, so here's the correct version:
int val; printf("Enter a value: "); fflush(stdout); scanf("%d", &val);
I'll explain what the problem is and why this fixes it later.
Anyway, I've been messing around with buffered IO these last few days, and I was
reminded of an experience I had when I first touched C, which I later saw happen
to others while I was at university. This
pretty much only appears in code for beginner programmers, and the examples are
often (usually?) incorrect.
K. N. King — C Programming: a Modern Approach
The aforementioned book contains the following code (with irrelevant parts
removed). I can't remember if I even used this book, and if so, whether this is
the code that left me chasing after a missing
fflush(), but my impression is
that the book gets recommended to beginners fairly often, so it's a good example.
int height, length, width; printf("Enter height of box: "); scanf("%d", &height); [...]
This code is incorrect.
It comes with a note that it "suffers from one problem" — that it doesn't
handle input that doesn't belong in an
int. This problem is one of a few
reasons that you should never use
scanf(), but there is another problem in
this code too.
University of [redacted]
My former university provided the following code (again, with irrelevant parts removed, and also reformatted). This code wasn't just an example, it formed part of an exercise, so we were supposed to be able to compile and run this code with no issues[1: coursework], and then write some code elsewhere to complete the exercise.
double a, b, c; printf ("Enter the values of a, b, and c respectively," " separated by white space then press Enter: "); scanf ("%lf%lf%lf", &a, &b, &c);
This code is also incorrect.
I watched someone try to do this exercise. He hadn't programmed in C before. The program didn't prompt for any input, but if you typed something and pressed "enter", the prompt would appear afterwards.
He was confused, and didn't know what was wrong. I told him that he needed to
printf(). After making this change, it worked as
His reaction was understandable. What's less understandable is how this happened in the first place. Did whoever wrote the code never run it? Did they not know this could happen? Did it work on their machine? I don't think I can come up with a charitable explanation. Besides, I know who wrote the code, and they don't deserve the benefit of the doubt.
Alternatively, why is someone who didn't know this could happen responsible for something like this in the first place?
This same pattern appeared in some coursework, but this time the code was
surrounded with a comment saying
/* DO NOT MODIFY THIS CODE */, or something similar.[back]
The Problem with the Code
So, let's have a look[2: docs]
fflush() does, exactly. It has a prototype of
int fflush(FILE *stream), which comes with a description of the function. The relevant part is:
streampoints to an output stream or an update stream in which the most recent operation was not input,
fflush()shall cause any unwritten data for that stream to be written to the file.
Unwritten data... output... that sounds familiar. So, we had some unwritten
data, which turned into written data when we called
fflush(). But why did we
have unwritten data in the first place? Well, we had to call
so let's investigate the
stdout part of that expression.
At program start-up, three streams shall be predefined and need not be opened explicitly: standard input (for reading conventional input), standard output (for writing conventional output), and standard error (for writing diagnostic output). When opened, the standard error stream is not fully buffered; the standard input and standard output streams are fully buffered if and only if the stream can be determined not to refer to an interactive device.
So, "fully buffered" is a thing, and a terminal sounds like a kind of
interactive device, which implies that
stdout is not fully buffered. Is
there some other kind of buffered?
When a stream is "unbuffered", bytes are intended to appear from the source or at the destination as soon as possible; otherwise, bytes may be accumulated and transmitted as a block. When a stream is "fully buffered", bytes are intended to be transmitted as a block when a buffer is filled. When a stream is "line buffered", bytes are intended to be transmitted as a block when a <newline> byte is encountered. Furthermore, bytes are intended to be transmitted as a block when a buffer is filled, when input is requested on an unbuffered stream, or when input is requested on a line-buffered stream that requires the transmission of bytes. Support for these characteristics is implementation-defined, and may be affected via setbuf() and setvbuf().
Input requested on a line-buffered stream? That sounds like something to do with
So now we know the following:
stdout is a stream, and we can infer that it's line buffered in this case
(since we don't need to call
fflush() if we print a newline), which means that
the software underlying the program is free to "accumulate and transmit as a
block" the bytes we want to print, which leaves us with unwritten bytes at a time
when this isn't desirable, so we need to call
fflush() to make sure that the
bytes are written immediately, since our output doesn't contain a newline.
Not that we didn't gloss over any details — after all, that first extract talks about "files", but I don't remember seeing any of those in our program. Or perhaps we could ask what a stream actually is.
And that, mystery student, is why you needed to add that extra line. Into someone else's code. In a language you'd never used before.
- You can find the definitions for libc functions in the C standard, at https://port70.net/~nsz/c/c11/n1570.html. However, the description is taken (along with all the other extracts) from https://pubs.opengroup.org/onlinepubs/9699919799/, in the POSIX.1-2017 specification, which has some of the same information as the relevant parts of the C standard, but phrased in a way that suits the article better.[back]