Go4Expert

Go4Expert (http://www.go4expert.com/)
-   C (http://www.go4expert.com/articles/c-tutorials/)
-   -   Solution to Problem When using scanf() before fgets() or gets() in C (http://www.go4expert.com/articles/solution-using-scanf-fgets-c-t27148/)

poornaMoksha 14Nov2011 08:56

Solution to Problem When using scanf() before fgets() or gets() in C
 
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!!!

lionaneesh 14Nov2011 21:58

Re: Solution to Problem When using scanf() before fgets() or gets() in C
 
This is what i asked you on email , There is another solution using sscanf() but i must say this one is much more simpler! :)

poornaMoksha 15Nov2011 17:50

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

Originally Posted by lionaneesh (Post 89216)
This is what i asked you on email , There is another solution using sscanf() but i must say this one is much more simpler! :)

Oh really...thanks!!!!!

wdliming 16Dec2011 10:23

Re: Solution to Problem When using scanf() before fgets() or gets() in C
 
really good !!~thank you for sharing!!

poornaMoksha 21Dec2011 15:28

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

Originally Posted by wdliming (Post 90335)
really good !!~thank you for sharing!!

You are welcome!!!!

hacktor 16Oct2013 16:18

Re: Solution to Problem When using scanf() before fgets() or gets() in C
 
Good approximation.


All times are GMT +5.5. The time now is 11:54.