scanf (and why you should avoid using it)
Thursday, February 7th, 2008
I believe scanf() is one of the most common pitfalls that most novice C programmers encounter. In our first steps with the C programming language we usually try to write some simple interactive programs, like a program that asks for our name ("What is your name?") and then sends some greetings in a more personalized fashion ("Hello, Giannis!"), or even some simple game.
In most books, students are encouraged to use the scanf() function as the standard method to read the user's input. And it really does a good job, as long as the input given by the user is what was expected by the author of the program. Or else, things may get a little tricky especially for the newcomer. Here is an example, a simple program that prompts the user to enter a number, and then it print out whether it was a even or an odd number. Here, our hypothetical programmer intended to apply some sort of defensive programming, and check whether the user's input was recognized as a number. The program will keep on asking until a valid number is entered:
At first sight it looks fine. If we try to run it we'll get what we expected:
It looks okay! But what happens if the user enters some invalid input? Let's find out:
And it goes like this until we terminate the program with Ctrl-C. Not quite what expected, eh?
What did go wrong here? Well, let's take a step-by-step course. At first, the user is prompted to enter a number. Then scanf is called to read an integer number from standard input. The program blocks and waits for the user to type something and press Enter. After that, scanf reads the first character from standard input, which is 'a', which is not a digit. As a result, scanf puts back the character and bails out. The error message is displayed and we start all over again to ask the user for new input. However, when scanf is called on second time, it doesn't block at all, because the input buffer already contains the previously entered data. It tries once more to parse the user's input, and it fails once again, and the loop infinitely continues for ever.
It turns out that the problem here is that if some invalid input is entered, it never leaves the input buffer. What we need here is to find a solution, so that the user input is always taken off from the input buffer, either when it is valid or not. The "proper" way to achieve this, is to first read a whole line of input into a temporary buffer using fgets(), and then parse the buffer using sscanf(), a close-cousin of scanf(). This is how it will look like:
And now let's try to test the new improved version of the program:
Now we got somewhere, didn't we? Another, less elegant, way to solve the same problem would be to still use scanf() but also meticulously take care to remove the invalid line of data from the buffer. In C-terms, that would be:
But as I said before, I personally find this version kind of ugly, because, we are using fgets() only to read and ignore a whole line from the input buffer, which is something we can avoid if we use sscanf().
I believe most C programmers have been puzzled at their first steps, and I hope that these little headaches will not discourage new programmers from continueing their efforts. For these efforts will be greatly rewarded!
In most books, students are encouraged to use the scanf() function as the standard method to read the user's input. And it really does a good job, as long as the input given by the user is what was expected by the author of the program. Or else, things may get a little tricky especially for the newcomer. Here is an example, a simple program that prompts the user to enter a number, and then it print out whether it was a even or an odd number. Here, our hypothetical programmer intended to apply some sort of defensive programming, and check whether the user's input was recognized as a number. The program will keep on asking until a valid number is entered:
#include <stdio.h>
int main()
{
int number, result;
do
{
printf("Give me a number: ");
result = scanf("%d", &number );
if (result < 1)
printf("You didn't type a number!\n");
}
while (result < 1);
if (number % 2 == 1)
printf("%d is odd.\n", number);
else
printf("%d is even.\n", number);
}
At first sight it looks fine. If we try to run it we'll get what we expected:
giannis@giannis-desktop:~$ ./a.out
Give me a number: 133
133 is odd.
giannis@giannis-desktop:~$ ./a.out
Give me a number: 44
44 is even.
It looks okay! But what happens if the user enters some invalid input? Let's find out:
Give me a number: abc
You didn't type a number!
Give me a number: You didn't type a number!
Give me a number: You didn't type a number!
Give me a number: You didn't type a number!
...
And it goes like this until we terminate the program with Ctrl-C. Not quite what expected, eh?
What did go wrong here? Well, let's take a step-by-step course. At first, the user is prompted to enter a number. Then scanf is called to read an integer number from standard input. The program blocks and waits for the user to type something and press Enter. After that, scanf reads the first character from standard input, which is 'a', which is not a digit. As a result, scanf puts back the character and bails out. The error message is displayed and we start all over again to ask the user for new input. However, when scanf is called on second time, it doesn't block at all, because the input buffer already contains the previously entered data. It tries once more to parse the user's input, and it fails once again, and the loop infinitely continues for ever.
It turns out that the problem here is that if some invalid input is entered, it never leaves the input buffer. What we need here is to find a solution, so that the user input is always taken off from the input buffer, either when it is valid or not. The "proper" way to achieve this, is to first read a whole line of input into a temporary buffer using fgets(), and then parse the buffer using sscanf(), a close-cousin of scanf(). This is how it will look like:
...
do
{
printf("Give me a number: ");
char st[1024];
fgets( st, sizeof(st), stdin );
result = sscanf(st, "%d", &number );
if (result < 1)
printf("You didn't type a number!\n");
}
while (result < 1);
...
And now let's try to test the new improved version of the program:
giannis@giannis-desktop:~$ ./a.out
Give me a number: rfdasf
You didn't type a number!
Give me a number: fasfdsfgsd
You didn't type a number!
Give me a number: 25
25 is odd.
Now we got somewhere, didn't we? Another, less elegant, way to solve the same problem would be to still use scanf() but also meticulously take care to remove the invalid line of data from the buffer. In C-terms, that would be:
...
do
{
printf("Give me a number: ");
result = scanf("%d", &number );
if (result < 1)
{
printf("You didn't type a number!\n");
char st[1024];
fgets( st, sizeof(st), stdin );
}
}
while (result < 1);
...
But as I said before, I personally find this version kind of ugly, because, we are using fgets() only to read and ignore a whole line from the input buffer, which is something we can avoid if we use sscanf().
I believe most C programmers have been puzzled at their first steps, and I hope that these little headaches will not discourage new programmers from continueing their efforts. For these efforts will be greatly rewarded!