Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly flush stdin in fgets loop

Tags:

c

stdin

I have found the example of clearing stdin using while((c = getchar()) != '\n' && c != EOF) on here a few times, and tried to use it in a loop that gets input via fgets. I need to flush, since the loop takes the \n character from the last input and runs with it again.

So what happens is that I have to press enter twice now. Why is this happening, and how can I fix this?

#define BUFFER_LIMIT 50
do
{
  int c;
  while ((c = getchar()) != '\n' && c != EOF);

  printf("console> ");
  fgets(input_buffer, BUFFER_LIMIT-1, stdin);

  if(do_something(input_buffer))
    break;
} while(strncmp(input_buffer, "quit", 4) != 0);
like image 303
BigBadWolf Avatar asked Dec 11 '15 08:12

BigBadWolf


3 Answers

There is unfortunately a lot of confusion about "flushing input" in C. The confusion arises almost entirely from one of the strange facts about the popular-but-flawed scanf function: it generally does not read full lines of input, and it generally leaves the newline character \n on the input stream after it converts its input. For example, if you write a program that says

printf("Type a number:\n");
scanf("%d", &n);

and if the user types "123" and hits the Return key, the number 123 will get stored into the variable n, but the \n character corresponding to the Return key will be left on the input stream. If the next thing your program does is to call fgets, or getchar, imagining that you'll begin reading the next line of input the user typed, your program will instead immediately read that leftover newline. It will seem as if the user typed an extra blank line, or something.

This problem is ridiculously widespread. Vast numbers of beginning C programmers have gotten stuck on it. In a nutshell, there are three recommended ways of fixing it:

  1. After calling a function like scanf that leaves the newline in the input buffer, and before calling a function like getchar or fgets that expects to start on a new line, use the little loop while((c = getchar()) != '\n' && c != EOF) to read and discard the newline that scanf left on the input buffer.

  2. Don't use scanf at all, or if you do, don't try to mix it with calls to getchar or fgets.

  3. (popular but badly problematic) Call fflush(stdin) to flush the unwanted input.

The problems with #3 have been widely discussed, so I won't say any more about those problems or that solution. The recommended "portable" alternative is #1, but it obviously works only if there is at least one unwanted newline in the input waiting to be flushed. If not, solution #1 will wrongly read and discard a line of wanted input, instead.

So you can't use #1 everywhere. You can't sprinkle it randomly in your program; you can't use it everywhere you might have used fflush(stdin). In general, as mentioned already, you'll need it only after calling scanf and before calling getchar or fgets.

In the code fragment in the question, there may not have been a need to use solution #1 at all. The fgets function is perfectly capable of cleanly reading individual lines of input all by itself. No additional flushing of input or discarding of newlines is necessarily required. (If there was a call to scanf down underneath do_something(), however, additional newline handling might have been necessary.)

like image 117
Steve Summit Avatar answered Sep 19 '22 16:09

Steve Summit


So what happens is that I have to press enter twice now. Why is this happening, and how can I fix this?

Well, that is what your code is doing - it first reads char-by-char until it finds newline. Then it calls fgets() which will... well, read until it finds a newline (probably char-by-char, but, also possibly in some other way).

You could try fflush(stdin), but that is not guaranteed to do what you want (it only gives guarantees for output buffers, not for input).

Also, you may try setbuf(stdin, NULL) which should disable buffering on standard input, so there would be nothing to flush. I tried this a few times on different systems and it worked, but documentation for this function is not 100% clear on this.

like image 40
srdjan.veljkovic Avatar answered Sep 20 '22 16:09

srdjan.veljkovic


My easy fix is making a buffer string char clear[2] and flushing the input buffer by writing the newline character to it whenever I use scanf by using gets(clear).

like image 24
gr33nd00r Avatar answered Sep 22 '22 16:09

gr33nd00r