user4087080
user4087080

Reputation:

How does fflush work?

I'm not sure if I properly understand how flushing works in C. I just can't get it to work as described in multiple manuals and reference books. Here's an example with comments:

#include <stdio.h>


int main(void) {
    int  x;
    char ch;

    printf("Prompt: ");
    scanf("%d", &x); /* I key in 67 and press Enter. At this very moment,
                        the input buffer should contain the ASCII codes
                        for the numbers 6 and 7 as well as the ASCII code
                        for the newline character, which is 10. The scanf
                        function is going to find the ASCII codes for 6
                        and 7, convert them to the integer 67, assign it
                        to the variable x and remove them from the
                        buffer. At this point, what still remains in the
                        buffer is the newline character. */
    fflush(stdin);   /* I want to do some more input. So, I flush the
                        buffer to remove anything that still might be
                        there, but it doesn't really look like fflush is
                        doing that. */

    printf("Prompt: "); /* I'm not going to be able to get my hands on
                           the following line of code because fflush is
                           not doing its job properly. The remaining
                           newline character is going to be read into the
                           variable ch automatically, thus not allowing
                           me to enter anything from the keyboard. */
    scanf("%c", &ch);

    printf("x: %d, ch: %d\n", x, ch);
    /*
       OUTPUT:
       Prompt: 67
       Prompt: x: 67, ch: 10
    */


    return 0;
}

Upvotes: 1

Views: 1037

Answers (3)

DevSolar
DevSolar

Reputation: 70213

Aside from fflush() not being defined on input streams as Sourav pointed out...

I key in 67 and press Enter. At this very moment, the input buffer should contain the ASCII codes for the numbers 6 and 7 as well as the ASCII code for the newline character, which is 10. The scanf function is going to find the ASCII codes for 6 and 7, convert them to the integer 67, assign it to the variable x and remove them from the buffer. At this point, what still remains in the buffer is the newline character.

And if the user did input something that is not a number, other things could have happened.

If there were non-digits before the first digit, x will not be initialized. You wouldn't know, because you did not check the return value of scanf().

The user might have anticipated the next prompt (for a character), and have entered something like 67 x, expecting the 67 to satisfy your first prompt, and the x your second. (And he won't be happy your program dropped part of his entry.)

Using *scanf() on input you cannot be sure is of the expected format (user input, as opposed to e.g. reading back something you yourself wrote with *printf()) is fragile.

So my generic advice is to not use the *scanf() functions on user input, but instead use fgets() to read user input line-wise, and parse the input in-memory at your leisure.

You have many much more powerful functions at your disposal that way, and you can handle error conditions more fine-grained (including the ability to give the full line entered in any error messages).

The below is only a rough scetch; depending on your applications, you'll want to organize this differently:

const size_t BUFFERSIZE = 1024;
char buffer[ BUFFERSIZE ];
long x;
char ch;
printf( "Prompt: " );
if ( fgets( buffer, BUFFERSIZE, stdin ) == NULL )
{
    // Error occurred, use feof() / ferror() as appropriate
}
else
{
    size_t len = strlen( buffer );
    if ( buffer[ len - 1 ] != '\n' )
    {
        // The line entered was too long for the buffer,
        // there is unread input. Handle accordingly, e.g.
        // resizing the buffer and reading the rest.
    }
    else
    {
        // You got the whole line; blot out the newline
        buffer[ len - 1 ] = '\0';

        // Assuming a decimal number first
        char * curr = buffer;
        errno = 0;
        long x = strtol( curr, &curr, 10 );
        if ( errno == ERANGE )
        {
            // Number exceeds "long" range, handle condition
        }
        else if ( curr == buffer )
        {
            // What you got was not a number, handle condition
        }

        // Keep parsing until you hit end-of-string
    }
}

Upvotes: 1

a3f
a3f

Reputation: 8657

For completion's sake:

Some implementations do define fflush on input streams. Examples are Microsoft's msvcrt and GNU libc. Others, like the BSD libc, may additionally provide a separate function for purging the buffer.

These functions, beside being unportable, have some serious drawback: They are often used with the assumption that the buffer looks some particular way, e.g. has a single newline.

stdin may be connected to a file, and its buffer might contain more than just a newline. These buffered data would be skipped over, if the input stream is purged.

Further, there can be buffers outside the libc's stdio which aren't affected.

Therefore, you should explicitly read and discard the data you don't want.

Upvotes: 2

Sourav Ghosh
Sourav Ghosh

Reputation: 134286

Don't do fflush(stdin);, it invokes undefined behavior.

Quoting C11,

Ifstream points to an output stream or an update stream in which the most recent operation was not input, the fflush function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.

and stdin is not an output stream.

Upvotes: 4

Related Questions