Keyboard buffer cleaning after scanf

Asked

Viewed 35,893 times

13

I’m having trouble with the job scanf();. When reading two or more values, the later values are not read.

I’ve already tried:

__fpurge(stdin);

After reading, but in this case I need to enter after each reading, which is a little uncomfortable for me.

I’ve also tried:

fflush(stdin);

But it doesn’t (I’m using Debian 7.1, I think fflush(stdin); only work on Windows)

What solved my problem was:

#include <stdio.h>

//Limpa o buffer do teclado
void flush_in(){ 
    int ch;
    while( (ch = fgetc(stdin)) != EOF && ch != '\n' ){} 
}

int main(){
    char c;

    printf("\nEntre com um caractere: ");
    scanf("%c",&c);
    flush_in(); 
    printf("\nO caractere: \"%c\" tem o valor ASCII %d", c ,(int)c);

    printf("\nEntre com um caractere: ");  
    scanf("%c",&c);
    flush_in();  
    //__fpurge ( stdin );
    printf("\nO caractere: \"%c\" tem o valor ASCII %d", c ,(int)c);

    printf("\nPress any key to exit...\n\n"); 
    getchar();
    return 0;
}

But the function code flush_in(), that I got here, is obscure to me.

So...

  1. Why this problem occurs?
  2. How to solve this problem so that the same code works in Windows and Linux environments after being compiled?
  3. What does the code snippet below?

    void flush_in(){
        int ch;
        while( (ch = fgetc(stdin)) != EOF && ch != '\n' ){}
    }
    
  • Please excuse the formatting. I could not format the last code snippet. I also could not put "flush_in();" in bold and italic. If anyone sees, please edit.

  • 1

    Because when a single character is read the terminator (generated by ENTER) continues in the buffer. The function discards this character. Note that there are other ways to discard this "dirt" of the input buffer, for example infoemndo in the next reading that should disregard an eventual ' n' present in the input buffer.

  • 1

    I fixed the formatting of that last block. To format code blocks within list items, you need to double indent.

  • Thank you/ I was unable to place. The title and tags improved a lot too.

6 answers

15


The problem is that when you type the following:

A [enter]
B [enter]

The following has been inserted into the input buffer:

A\nB\n

When performing scanf("%c", &c) you read a single character from the buffer. In case it is A.

\nB\n

Notice that at no time the \n was consumed. In the next scanf("%c", &c), it will be read. Then c = '\n' and the buffer is:

B\n

And here lies the error. The expected behavior was to have read A in the first and B in the second.

The fix for this, however, is quite simple and does not need any additional function. Simply consume the line break! Use scanf("%c\n", &c).

As to what function flush_in do, observe:

void flush_in() {
    int ch;
    do {
        ch = fgetc(stdin)
    } while (ch != EOF && ch != '\n');
}

It will repeatedly read the input buffer until it finds a line break or until the buffer runs out. That is, it will consume the entire current line.

  • EOF (-1), how does it work? Could you explain more about it?

  • 1

    When reading from a stream that can be finished and there is nothing left to read, a read operation will return EOF. In the case of stdin, you can squeeze Ctrl+D at the terminal to close the stream (any read attempt will result in EOF instead of blocking waiting for the user to type in something). In the case of flush_in is there to avoid an infinite loop (since the reading would be eternally returning EOF).

  • 1

    Isn’t it a little performative to read each character individually? I have the impression that it is more efficient to use the solution of the `fgets' proposed by @pmg

4

The best (flawless, compatible with all systems) way to read user input is to use only fgets() to obtain data (do not mix with scanf(), getchar(), or other read functions).

Every time you make one fgets() it must be checked if you read a complete line (the last element of the string read is '\n') and otherwise continue reading by increasing the buffer size or ignoring the extra characters.

After the complete line has been obtained (and the '\n' removed if you want) you should treat that line with sscanf(), strstr(), strtol(), strtod(), direct access to characters, ...

#include <stdio.h>
#include <string.h>

int main(void) {
    char buffer[80];

    for (int k = 0; k < 2; k++) {
        printf("Entre com um caractere: ");
        fflush(stdout);
        fgets(buffer, sizeof buffer, stdin);
        if (buffer[strlen(buffer) - 1] != '\n') {
            // linha muito longa, ignorar o resto usando a parte final do buffer
            do {
                fgets(buffer + 1, sizeof buffer - 1, stdin);
            } while (buffer[strlen(buffer) - 1] != '\n');
        }
        printf("O caractere: \"%c\" tem o valor ASCII %d\n", *buffer, *buffer);
    }

    printf("Press ENTER to exit...\n");
    do {
        fgets(buffer, sizeof buffer, stdin);
    } while (buffer[strlen(buffer) - 1] != '\n');

    return 0;
}

1

Can be used this code here which also works perfect:

while ((c = getchar()) != '\n' && c != EOF) {}

1

There are several ways to clear the buffer (temporary storage area) of the keyboard. The most suitable is, of course, the following and has already been mentioned. It is the most recommended for being practical, simple and portable (works on more than one operating system).

void clear_keyboard_buffer(void) { int c = 0; while ((c = getchar()) != '\n' && c != EOF) {} return; }

As already explained, this code consumes, cleans, Zera the buffer.

  • to consume the rest of the buffer just do: "scanf("%*[ n]");", does not need the loop; in the next scanf the " n" that remained in will be consumed by the formats "%s", "%d", etc; in the case of the format "%c", when placing a space before the format all the tablets and cr/lf are consumed, thus: "scanf(" %c", &ch);"...in my answer I give a complete example

0

#include <stdio.h>

int main()
{
   char c;

   printf("\nEntre com um caracter: ");
   scanf(" %c%*[^\n]",&c);
   printf("\nO caracter: \"%c\" tem o valor ASCII %d", c ,(int)c);

   printf("\nEntre com um caracter: ");
   scanf(" %c%*[^\n]",&c);
   printf("\nO caracter: \"%c\" tem o valor ASCII %d", c ,(int)c);

   printf("\nPress any key to exit...\n\n");
   getchar(); // consome '\n' deixado plo scanf
   getchar(); // aguarda ENTER do teclado
}

Explanation of scanf(" %c%*[^\n]",&c);:

  1. pula whitespace (among which ' ', ' t' and ' n')
  2. reads an X character that is not whitespace
  3. skip all characters until you find a ' n' (n' stays in the buffer)

Next scanf the ' n' that was left in the buffer will be consumed in step 1.

0

As mentioned by William Bernal, whenever a char is read with scanf("%c", &x); we have this problem. A practical way to solve is to give a getchar() after reading, getting:

scanf("%c", &x);
getchar();

Still, the safest way to treat this type of problem is with fgets(), as @pmg mentioned.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.