Solution to Problem When using scanf() before fgets() or gets() in C

Discussion in 'C' started by poornaMoksha, Nov 14, 2011.

  1. poornaMoksha

    poornaMoksha New Member

    Joined:
    Jan 29, 2011
    Messages:
    150
    Likes Received:
    33
    Trophy Points:
    0
    Occupation:
    Software developer
    Location:
    India
    There are a certain functions in C, which if used in a particular combination may cause problems. These problems are due to the conflicting behaviors of the functions. In this article, we will understand the problem caused by using one such combination of functions scanf() and fgets()/gets().

    But before that, lets first understand what does these two functions do?

    Understanding scanf()



    scanf() function reads input from the standard input stream stdin.

    Lets understand the working of scanf() through a piece of code :

    Code:
    #include <stdio.h> 
    #include <string.h> 
    int main() 
    { 
          int n; 
          printf("Enter a number:"); 
          scanf("%d",&n); 
          printf("You entered %d \n",n); 
          return 0; 
    }
    In the above piece of code, the logic asks the user to enter a number (like 123, 45 etc)and then it uses scanf() API to store it as a value for the variable 'n'. The format specifier we used in scanf() this time is '%d'. This format specifier tells the API to treat the input as integer value and terminate if :

    1. The values in stdin are no more integer.
    2. No more values are there in stdin.

    Lets run the above code :

    Code:
    ~/practice $ ./scanf  
    Enter a number:123 
    You entered 123 
     ~/practice $ vim scanf.c 
     ~/practice $ ./scanf  
    Enter a number:123ad34 
    You entered 123
    As you can see that I ran the executable twice above. For the first time all the visible input is read(since all the input is integer values) and the API terminates after that while in the second case the input is read until a non-integer value 'a' is encountered.

    Understanding fegts()



    fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A '\0' is stored after the last character in the buffer.

    Lets understand the working of fgets() through an example :

    Code:
    #include <stdio.h> 
    #include <string.h> 
    int main() 
    { 
          char buff[100]; 
          memset(buff,0,sizeof(buff)); 
          printf("\n Enter a name:"); 
          fgets(buff,sizeof(buff),stdin); 
          printf("\n The name entered is %s\n",buff); 
          return 0; 
    }
    In the above piece if code, we declared a buffer of 100 characters to hold a name. Then we set the buffer with all zeros so that there is no garbage values in the buffer before it is used. Next, we ask the user to enter a name and use the API fgets() to fetch the name into the buffer from stdin. fgets() stops reading after an EOF or newline character has been read.

    Lets see the output of the code above:

    Code:
    ~/practice $ ./scanf  
     
     Enter a name:Himanshu 
     
     The name entered is Himanshu 
     
     himanshu@himanshu-laptop ~/practice $ ./scanf  
     
     Enter a name:John123 
     
     The name entered is John123 
     
    himanshu@himanshu-laptop ~/practice $ ./scanf  
     
     Enter a name:123John 
     
     The name entered is 123John
    I ran the executable thrice with different inputs and saw that all the input is taken by the API as name(as required by our code)

    The Problem



    The problem will there be in using scanf() API just before fgets().

    Lets go through a piece of code to demonstrate this first :

    Code:
    #include <stdio.h> 
    #include <string.h> 
    int main() 
    { 
          int n; 
          char buff[100]; 
          memset(buff,0,sizeof(buff)); 
          printf("Enter a number:"); 
          scanf("%d",&n); 
          printf("You entered %d \n",n); 
          printf("\n Enter a name:"); 
          fgets(buff,sizeof(buff),stdin); 
          printf("\n The name entered is %s\n",buff); 
          return 0; 
    }
    In the above piece of code, we try to get a number from stdin through the API scanf(). Then we use the API fgets() to get name from stdin.

    Lets see whats the output :

    Code:
    ~/practice $ ./scanf  
    Enter a number:123 
    You entered 123  
     
     Enter a name: 
     The name entered is  
     
     ~/practice $ ./scanf  
    Enter a number:123abc145 
    You entered 123  
     
     Enter a name: 
     The name entered is abc145
    As you can see above, I ran the executable twice and I got some weird results :

    1. In the first run, the execution did not stop at stdin when the fgets() API was being executed. So, I could not enter a name and hence no name was displayed.
    2. In the second run also the execution did not stop at stdin when fgets() API was being executed. So, I could not enter a name but the weirder part is that the last line of the output shows few final bytes of the input given to scanf() API being stored as the value of name.

    Now, for a newbie this situation is like hell as he/she may think that the fgets() API is doing something wrong as the API should be able to at least stop the execution at the stdin.

    In real life as far as my experience goes, I have seen some students and even some professionals seeking help of their mentors on this type of issue and all they get in return is :

    " There might be some garbage lingering around after the call to scanf(). Use fflush() and things should work fine".

    Lets use fflush() in our code and see if it does any good to us :

    Code:
    #include <stdio.h> 
    #include <string.h> 
    int main() 
    { 
          int n; 
          char buff[100]; 
          memset(buff,0,sizeof(buff)); 
          printf("Enter a number:"); 
          scanf("%d",&n); 
          printf("You entered %d \n",n); 
          fflush(stdin); 
          printf("\n Enter a name:"); 
          fgets(buff,sizeof(buff),stdin); 
          printf("\n The name entered is %s\n",buff); 
          return 0; 
    }
    Lets see the output :

    Code:
    ~/practice $ ./scanf  
    Enter a number:123 
    You entered 123  
     
     Enter a name: 
     The name entered is
    Well, I heard that with some old compilers this techniques used to work but with the new compilers like gcc(which I am using), the output clearly shows no improvement.

    God bless these mentors. Why? Lets read further to find the reason.

    Firstly, lets see what fflush() do?

    From the man page of fflush :


    NAME fflush - flush a stream

    SYNOPSIS
    #include <stdio.h>

    int fflush(FILE *stream);

    DESCRIPTION - For output streams, fflush() forces a write of all user-space buffered data for the given output or update stream via the stream's underlying write function. For input streams, fflush() discards any buffered data that has been fetched from the underlying file, but has not been by the application. The open status of the stream is unaffected.

    If the stream argument is NULL, fflush() flushes all open output streams.

    For a non-locking counterpart, see unlocked_stdio(3).

    RETURN VALUE - Upon successful completion 0 is returned. Otherwise, EOF is returned and errno is set to indicate the error.
    Look at the description part above. It clearly says that fflush do not work for input streams and yes, stdin is an input stream. Now, Its above my head why these great mentors used fflush() as the solution to this problem? Also, the bigger surprise is that with some old compilers, this wrong solution used to work too.

    NOTE : If somebody has the reason why it used to work with some old compilers, please do tell me.

    Now, lets analyze the problem our way and try to understand whats going on?

    1. First of all, recall what scanf() does. It reads the input from stdin according to the type specifier provided to it. Like it will read only integers from stdin, if '%d' is specified. So, at the occurrence of first non integer value on stdin, the API will return.
    2. Now, lets see what did we enter at the stdin when we entered the number: 1 2 3 \n
    3. Though visibly we entered 123 but its a trailing '\n' that we entered after 123 by pressing 'Enter' from the keyboard.
    4. So, what the scanf() API did is it took 123 from stdin and returned when found '\n'. So after scanf() returned, stdin looks like : \n . There is only one character '\n' left on stdin.
    5. Now, when the flow reaches fgets(), this API first looks at stdin for input and WHOLA!!! it finds a stray '\n' there. Lets recall the description of fgets() which said that this API returns when it encounters EOF or '\n'. BANG ON!!! now you know what happened, since fgets() got '\n' from stdin so it executed without waiting for user to enter something as the APIs returning conditions met.

    If you understood the above explanation then same goes for the second case where the input for scanf() was

    1 2 3 a b c 1 4 5

    1. scanf() API read 123 and returned. Now, stdin was left with : a b c 1 4 5 \n
    2. Now, fgets() was executed and it saw some values already present at stdin and it returned with these remaining values without waiting for user to enter the values and hence we saw the output as abc145.

    As you see that the problem wasn't that tricky but its all about how well you know the APIs you use in your code.

    How to get around



    Now, the next question is how to get around the above discussed problem.

    • An easy way out would be to use getchar() and ignore the extra characters left by scanf().
    Look at the code :

    Code:
    #include <stdio.h> 
    #include <string.h> 
      int main() 
      { 
          int n; 
          char buff[100]; 
          memset(buff,0,sizeof(buff)); 
          printf("Enter a number:"); 
          scanf("%d",&n); 
          printf("You entered %d \n",n); 
          getchar(); 
          printf("\n Enter a name:"); 
          fgets(buff,sizeof(buff),stdin); 
          printf("\n The name entered is %s\n",buff); 
          return 0; 
      }
    Lets look at the output :

    Code:
    ~/practice $ ./scanf  
    Enter a number:123 
    You entered 123  
     
     Enter a name:Himanshu 
     
     The name entered is Himanshu
    As seen from the output, this time the code worked as expected because we ignored the stray '\n' left behind at stdin by scanf().

    But again, this might be an easy solution but not an ideal one as we need to ignore all the characters left behind (not always one, like we did in above example).
    • The ideal solution to this problem is to avoid using this combination of functions and try to get the work done with some other set of functions which pose no problems of this kind.

    Conclusion



    To conclude, this article explained the problem one can face by using a combination of two APIs which when individually used cause no problems. The focus of this article was the problems caused by using a combination of scanf() and fgets().

    Stay tuned for more!!!
     
    hacktor and lionaneesh like this.
  2. lionaneesh

    lionaneesh Active Member

    Joined:
    Mar 21, 2010
    Messages:
    848
    Likes Received:
    224
    Trophy Points:
    43
    Occupation:
    Student
    Location:
    India
    This is what i asked you on email , There is another solution using sscanf() but i must say this one is much more simpler! :)
     
  3. poornaMoksha

    poornaMoksha New Member

    Joined:
    Jan 29, 2011
    Messages:
    150
    Likes Received:
    33
    Trophy Points:
    0
    Occupation:
    Software developer
    Location:
    India
    Oh really...thanks!!!!!
     
  4. wdliming

    wdliming New Member

    Joined:
    Sep 26, 2010
    Messages:
    28
    Likes Received:
    0
    Trophy Points:
    0
    really good !!~thank you for sharing!!
     
  5. poornaMoksha

    poornaMoksha New Member

    Joined:
    Jan 29, 2011
    Messages:
    150
    Likes Received:
    33
    Trophy Points:
    0
    Occupation:
    Software developer
    Location:
    India
    You are welcome!!!!
     
  6. hacktor

    hacktor New Member

    Joined:
    Oct 16, 2013
    Messages:
    1
    Likes Received:
    0
    Trophy Points:
    0
    Good approximation.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice