C: Clearing STDIN

2019-08-26 03:07发布

问题:

basically in codeblocks for windows before each printf I have "fflush(stdin);" which works. When I copied my code to Linux, it doesn't work, nor does any of the alternatives for "fflush(stdin);" that I've found. No matter which way I seem to do it, the input doesn't seem to be clearing in the buffer or something in my code is incorrect.

#include <stdio.h>
#include <math.h>
#include <limits.h>
#include <ctype.h>

int main()
{
   char pbuffer[10], qbuffer[10], kbuffer[10];
   int p=0, q=0, k=0;
   int r, i, Q, count, sum;
   char a[3];
   a[0]='y';
   while(a[0]=='y' || a[0]=='Y')
   {
      printf("Enter a p value: \n");
      fgets(pbuffer, sizeof(pbuffer), stdin);
      p = strtol(pbuffer, (char **)NULL, 10);

      printf("Enter a q value: \n");
      fgets(qbuffer, sizeof(qbuffer), stdin);
      q = strtol(qbuffer, (char **)NULL, 10);

      printf("Enter a k value: \n");
      fgets(kbuffer, sizeof(kbuffer), stdin);
      k = strtol(kbuffer, (char **)NULL, 10);

      while(p<q+1)
      {
         Q=p;
         sum=0;
         count=0;
         while(Q>0)
         {
            count++;
            r = Q%10;
            sum = sum + pow(r,k);
            Q = Q/10;
         }

         if ( p == sum && i>1 && count==k )
         {
            printf("%d\n",p);

         }
         p++;
         a[0]='z';
      }
      while((a[0]!='y') && (a[0]='Y') && (a[0]!='n') && (a[0]!='N'))
      {
         printf("Would you like to run again? (y/n) ");
         fgets(a, sizeof(a), stdin);
      }
   }
   return 0;
}

回答1:

Calling fflush(stdin) is not standard, so the behavior is undefined (see this answer for more information).

Rather than calling fflush on stdin, you could call scanf, passing a format string instructing the function to read everything up to and including the newline '\n' character, like this:

scanf("%*[^\n]%1*[\n]");

The asterisk tells scanf to ignore the result.

Another problem is calling scanf to read a character into variable a with the format specifier of " %s": when the user enters a non-empty string, null terminator creates buffer overrun, causing undefined behavior (char a is a buffer of one character; string "y" has two characters - {'y', '\0'}, with the second character written past the end of the buffer). You should change a to a buffer that has several characters, and pass that limit to scanf:

char a[2];
do {
    printf("Would you like to run again? (y/n) \n")
    scanf("%1s", a);
} while(a[0] !='y' && a[0] !='Y' && a[0]!='n' && a[0]!='N' );
}


回答2:

I think what you are trying to do is more difficult than it seems.

My interpretation of what you are trying to do is disable type ahead so that if the user types some characters while your program is processing other stuff, they don't appear at the prompt. This is actually quite difficult to do because it is an OS level function.

You could do a non blocking read on the device before printing the prompt until you get EWOULDBLOCK in errno. Or the tcsetattr function family might help. It looks like there is a way to drain input for a file descriptor in there, but it might interact badly with fgets/fscanf

A better idea is not to worry about it at all. Unix users are used to having type ahead and what you want would be unexpected behaviour for them.



回答3:

Drop the need for flushing the input buffer.

OP is on the right track using fgets() rather than scanf() for input, OP should continue that approach with:

char a;
while(a !='y' && a !='Y' && a!='n' && a!='N' ) {

  printf("Would you like to run again? (y/n) \n");
  if (fgets(kbuffer, sizeof(kbuffer), stdin) == NULL) 
    Handle_EOForIOerror();

  int cnt = sscanf(kbuffer, " %c", &a); // Use %c, not %s
  if (cnt == 0) 
    continue; // Only white-space entered
  }

Best to not use scanf() as it tries to handle user IO and parsing in one shot and does neither that well.


Certain present OP's woes stem from fgets() after scanf(" %s", &a); (which is UB as it should be scanf(" %c", &a);. Mixing scanf() with fgets() typically has the problem that the scanf(" %c", &a); leaves the Enter or '\n' in the input buffer obliging the code to want to flsuh the input buffer before the next fgets(). Else that fgets() gets the stale '\n' and not a new line of info.

By only using fgets() for user IO, there need for flushing is negated.


Sample fgets() wrapper

char *prompt_fgets(const char *prompt, char dest, long size) {
  fputs(prompt, stdout);
  char *retval = fgets(dest, size, stdin);
  if (retval != NULL) {
    size_t len = strlen(dest);
    if (len > 1 && dest[len-1] == '\n') { // Consume trailing \n
      dest[--len] = '\0';
    }
    else if (len + 1 == dest) {  // Consume extra char
      int ch;
      do {
        ch == fgetc(stdin);
      } while (ch != '\n' && ch != EOF);
    }
  return retval;
  }