scanf and other fun

Incorrect Code

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 printf() and scanf() pattern 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, 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 call fflush(stdout) after printf(). After making this change, it worked as intended.

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.

The Problem with the Code

So, let's have a look at what fflush() does, exactly. It has a prototype of int fflush(FILE *stream), which comes with a description of the function. The relevant part is:

If stream points 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 fflush(stdout), 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 scanf()...

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.