Thursday, October 14, 2010

scanf() is nasty!! Is it?



If you have begun C programming, chances are you have already faced or you will very soon face the nasty behaviour of scanf while inputting characters. If you have faced this problem and didn't get a solution yet, read on. If you haven't faced this problem, you'll do very soon, so read on :P

The problem
Consider the following C program:

#include <stdio.h>
int main(){
    char ch[10];
    int i[10], j=0;
    while(j<10){
      printf("Please enter a character: ");
      scanf("%c",&ch[j]);
      printf("Please enter an integer: ");
      scanf("%d",&i[j]);
      j++;
     }  
    j=0;
    printf("Char\tInt\n");
    printf("____\t____\n");
    while(j<10){
    printf("%c\t%d\n",ch[j],i[j]);
    j++;
    }
    return 0;
    } 

The program is very simple. It takes a character and a number as input, 10 times and then outputs them neatly in a table format. Is it so? The program code looks like that. Now compile the program with gcc and run it. The behaviour you'll see is unexpected! Then what went wrong?

The cause
We have used the format specifier %c with the first scanf. That is the root of the problem! What happens is that, when you enter a character, for example a and press enter, actually two characters go into the standard input stream. First is the a and second is the \n i.e. the newline character corresponding to your enter press. %c reads the single character i.e. a and stores it in the variable. Next you enter anything as integer, say 1. The second scanf skips the \n that was left after the because it doesn't look like an integer. Then it encounters 1 which is followed by a \n so it stores the 1 into the integer variable. In the next iteration of the loop the %c in the scanf directly takes the \n from the stdin that was entered after the 1 and you don't even get a chance to enter a character. This process goes on and on and you never get a chance to enter any character. You are prompted to enter integer only, during each iteration of the loop. Hope you got it ;-) To confirm, just enter %d as the format specifier of printf instead of %c for the character variable in line 16. You'll get the ASCII value of newline character i.e. 10 printed neatly in the table in place of characters in the output.

The solution
There are several solutions to this problem.
The first one I saw in a forum was to clean the stdin. A funcion proposed to do that was as follows:
void clean_stdin(void)
{
int c;

do
{
c = fgetc(stdin);
}
while (c != '\n' && c != EOF);
}
and you're supposed to call this after every time you scanf a characer with the %c format specifier. I tried it, and it worked.
Another solution is the put a getchar() line after every scanf. It will take the trailing newline character and put into oblivion :P
Yet another solution, that I loved was to put a trailing space before the %c like this:
scanf(" %c",&ch[j]);
What the space before %c do, is that, it makes the scanf skip any kind of trailing whitespaces and then reads any non-whitespace character and puts into the character variable.
In fact, as you now know the reason of the issue you can develop as many solutions as you want.

No comments: