I have the following code:
#include <stdio.h>
#define MIN 0
#define MAX 9
int main()
{
int n;
while (1) {
printf("Enter a number (%d-%d) :", MIN, MAX);
scanf("%d", &n);
if (n >= MIN && n <= MAX) {
printf("Good\n");
} else {
printf("Damn you!\n");
break;
}
}
return 0;
}
The above code works as expected as long as the user inputs an integer value. For example,
$ ./a.out
Enter a number (0-9) :15
Damn you!
$ ./a.out
Enter a number (0-9) :5
Good
Enter a number (0-9) :3
Good
Enter a number (0-9) :-1
Damn you!
$ ./a.out
But, when the user enters any unexpected input (like <up-arrow>
- which is ^[[A
, or any string like abc
or abc def
, etc), it fails and goes in to an infinite loop.
$ ./a.out
Enter a number (0-9) :2
Good
Enter a number (0-9) :^[[A
Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
Enter a number (0-9) :Good
^C
One thing to note: when the use enters <up-arrow>
for the first time, it works as expected! For example,
$ ./a.out
Enter a number (0-9) :^[[A
Damn you!
$
Why is this odd behavior? How should we handle the case where user enters something that is unappropriate?
Personally, I advise ditching
scanf
altogether for interactive user input, especially for numeric input. It just isn't robust enough to handle certain bad cases.The
%d
conversion specifier tellsscanf
to read up to the next non-numeric character (ignoring any leading whitespace). Assume the callIf your input stream looks like {'\n', '\t', ' ', '1', '2', '3', '\n'},
scanf
will skip over the leading whitespace characters, read and convert "123", and stop at the trailing newline character. The value123
will be assigned toval
, andscanf
will return a value of 1, indicating the number of successful assignments.If your input stream looks like {'a', 'b', 'c', '\n'},
scanf
will stop reading at thea
, not assign anything toval
, and return 0 (indicating no successful assignments).So far, so good, right? Well, here's an ugly case: suppose your user types in "12w4". You'd probably like to reject this entire input as invalid. Unfortunately,
scanf
will happily convert and assign the "12" and leave the "w4" in the input stream, fouling up the next read. It will return a 1, indicating a successful assignment.Here's another ugly case: suppose your user types in an obnoxiously long number, like "1234567890123456789012345678901234567890". Again, you'd probably like to reject this input outright, but
scanf
will go ahead and convert and assign it, regardless of whether the target data type can represent that value or not.To properly handle those cases, you need to use a different tool. A better option is to read the input as text using
fgets
(protecting against buffer overflows), and manually convert the string usingstrtol
. Advantages: you can detect and reject bad strings like "12w4", you can reject inputs that are obviously too long and out of range, and you don't leave any garbage in the input stream. Disadvantages: it's a bit more work.Here's an example:
My advice would be to check the return value of scanf(). If it is zero, there has been a matching failure (ie the user didn't input an integer).
The reason it is succeeding is because n is not altered by scanf() when the match fails, so the check is performed on an uninitialised 'n'. My advice -there- would be to always initialise everything so that you don't end up getting weird logic results like you have there.
For example:
I have updated the code as follows (checked
scanf()
return value) and it works fine.The following are few things to note from the
scanf()
man page!man scanf
I would use a
char
buffer to get the input and then convert it to an integer with e.g.atoi
. Only problem here is thatatoi
returns 0 on failure (you can't determine if it's 0 because of failure or because the value is 0).Also you could just compare the strings with
strncmp
.// edit:
As suggested in the comments you can do the check with
isdigit()
Since I'm a bit in a hurry I couldn't implemented my example in your use case, but I also doubt that this causes any troubles.Some example code would be:
If the first element of the buffer is not a digit you know its wrong input anyway.
Otherwise you check the value now with
atoi
and look if its greater than 9. ( You don't need to check the lower value since -1 would already be detected in theisdigt
call (buf[0]
would be "-" )scanf return the number of fields it read, so you can do something like
if (scanf("%d",&n)<1) exit(1)
or even: