To echo what I have posted in another answer about C++: I suggest to toss scanf()
away, to never use it, and to instead use fgets()
and sscanf()
.
The reason for this is, that at least in Unix-like systems by default, the terminal your CLI program runs on does some processing of the user input before your program sees it. It buffers input until a newline is entered, and allows for some rudimentary line editing, like making backspace work.
So, you can never get a single character at a time, or a few single characters, just a full line. But that's not what e.g. scanf("%d")
processes, instead it processes just the digits, and stops there, leaving the rest buffered in the C library, for a future stdio
function to use. If your program has e.g.
printf("Enter a number: ");
scanf("%d", &a);
printf("Enter a word: ");
scanf("%s", word);
and you enter the line 123 abcd
, it completes both scanf()
s at once, but only after a newline is given. The first scanf()
doesn't return when a user has hit space, even though that's where the number ends (because at that point the line is still in the terminal's line buffer); and the second scanf()
doesn't wait for you to enter another line (because the input buffer already contains enough to fill the %s
conversion).
This isn't what users usually expect!
Instead, they expect that hitting enter completes the input, and if you hit enter, you either get a default value, or an error, with possibly a suggestion to please really just give the answer.
You can't really do that with scanf("%d")
. If the user just hits enter, nothing happens. Because scanf()
is still waiting for the number. The terminal sends the line onward, but your program doesn't see it, because scanf()
eats it. You don't get a chance to react to the user's mistake.
That's also not very useful.
Hence, I suggest using fgets()
or getline()
to read a full line of input at a time. This exactly matches what the terminal gives, and always gives your program control after the user has entered a line. What you do with the input line is up to you, if you want a number, you can use atoi()
, strtol()
, or even sscanf(buf, "%d", &a)
to parse the number. sscanf()
doesn't have the same mismatch as scanf()
, because the buffer it reads from is limited in size, and when it ends, it ends -- the function can't wait for more.
(fscanf()
on a regular file can also be fine if the file format is one that supports how it skims over newlines like any whitespace. For line-oriented data, I'd still use fgets()
and sscanf()
.)
So, instead of what I had above, use something like this:
printf("Enter a number: ");
fgets(buf, bufsize, stdin);
sscanf(buf, "%d", &a);
or, actually, check the return value of sscanf()
too, so you can detect empty lines and otherwise invalid data:
#include <stdio.h>
int main(void)
{
const int bufsize = 100;
char buf[bufsize];
int a;
int ret;
char word[bufsize];
printf("Enter a number: ");
fgets(buf, bufsize, stdin);
ret = sscanf(buf, "%d", &a);
if (ret != 1) {
fprintf(stderr, "Ok, you don't have to.\n");
return 1;
}
printf("Enter a word: ");
fgets(buf, bufsize, stdin);
ret = sscanf(buf, "%s", word);
if (ret != 1) {
fprintf(stderr, "You make me sad.\n");
return 1;
}
printf("You entered %d and %s\n", a, word);
}
Of course, if you want the program to insist, you can create a simple function to loop over the fgets()
and sscanf()
until the user deigns to do what they're told; or to just exit with an error immediately. Depends on what you think your program should do if the user doesn't want to play ball.
You could do something similar e.g. by looping over getchar()
to read characters until a newline after scanf("%d")
returned, thus clearing up any garbage left in the buffer, but that doesn't do anything about the case where the user just hits enter on an empty line. Anyway, fgets()
would read until a newline, so you don't have to do it yourself.
fflush(stdin)
can be used before the call toscanf()
for a single character. Please read Usingfflush(stdin)
for a discussion of the pros and cons and alternatives to that method (which works, more or less, on Windows, and does not work most other places). – Advertisingfflush(stdin)
is plain undefined behaviour. – Arlettaarlette