3

This is a seemingly simple question that I have not been able to answer for far too long:

I am trying to read input from a user in a C program using fgets(). However, I am running into the problem that if the user enters more characters than fgets() is set to read, the next call to read a string from the user automatically reads the remaining characters in the stdin buffer, and this is NOT behavior I want.

I have tried many ways to clear the stdin stream, and while I know something like

while(getchar()!='\n'); will work, this requires the user to hit enter an additional time which is not something I want.

The structure of the code looks something like this:

    void read_string(char *s, int width){
        fgets(s,width,stdin);
        clear_stdin();
        .
        .   
    }

    while (1){
        read_string()
        .
        .
    }

But I cannot get a clear_stdin() function that works desirably. How on earth can I clear the stdin, without having the user needlessly need to hit enter twice?

xjka
  • 55
  • 2
  • 5
  • 1
    A common fairly portable way is simply to loop using `getchar()` until `EOF` is encountered, e.g. `void clear_stdin(void) { int c = getchar(); while (c != EOF) c = getchar(); }` (or the `'\n'` if you simply need to erase the remainder of the user input, e.g. `while (c != '\n' && c != EOF)`) – David C. Rankin Oct 03 '18 at 04:56
  • 1
    You may want to look at [Using `fflush(stdin)`](http://stackoverflow.com/questions/2979209/using-fflushstdin), primarily to look at the alternatives discussed to using `fflush(stdin)` which is undefined according to standard C but some systems, notably Microsoft's, provide a defined meaning for. – Jonathan Leffler Oct 03 '18 at 05:01
  • 1
    Silently ignoring stuff that the user has typed is probably not what most users expect from a well behaved program. You probably want to inform them that their input is not what your program expects. At any rate it seems that your program doesn't like lines that are too long. If so, I woild recommend this pattern: 1 read an *entire* line; 2 check if it's too long. Note that `fgets` may or may not read an entire line. You cannot continue reading to the end of the line without checking whether `fgets` has already done that. – n. m. could be an AI Oct 03 '18 at 05:04
  • For a link to an SO question to show up in the 'linked' section on the RHS, it must be a current `https://` URL (e.g. [Using `fflush(stdin)`](https://stackoverflow.com/questions/2979209/using-fflushstdin)) and not an `http://` link as I accidentally used in my previous [comment](https://stackoverflow.com/questions/52619841/how-can-i-clear-stdin-in-a-c-progam/52620225#comment92173738_52619841). – Jonathan Leffler Oct 03 '18 at 06:08
  • 1
    https://stackoverflow.com/questions/35178520/how-to-read-parse-input-in-c-the-faq – Lundin Oct 03 '18 at 06:44

3 Answers3

4

You cannot clear stdin in a portable way (because no function from <stdio.h> is specified doing that). BTW, stdin can usually be not only a terminal, but also a redirection or a pipe (or even perhaps some socket). Details matter of course (e.g. your operating system and/or running environment).

You could avoid stdio and use operating system specific ways to deal with standard input (e.g. working at the file descriptor level on POSIX systems).

On Linux (specifically) you might read more about the Tty demystified, and code low level code based on such knowledge. See termios(3). Consider using readline(3).

You could use (on Linux at least) getline(3) to read a heap-allocated line buffer.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
4

To achieve what you want — reading and ignoring extra characters up to a newline if the buffer you supplied is over-filled — you need to conditionally read up to the newline, only doing so if there isn't a newline already in the input buffer that was read.

void read_string(char *s, int width)
{
    if (fgets(s, width, stdin) != 0)
    {
        size_t length = strlen(s);
        if (length > 0 && s[length-1] != '\n')
        {
            int c;
            while ((c = getchar()) != '\n' && c != EOF)
                ;
        }
        /* Use the input data */
    }
    else
        /* Handle EOF or error */
}

The other part of the technique is to make sure that you use a big enough buffer that it is unlikely that anyone will overflow it. Think in terms of char buffer[4096]; as an opening bid; you can increase it if you prefer. That makes it unlikely that anyone will (be able to) type enough data on a single line to overflow the buffer you provide, thus avoiding the problem.

Another option is to use POSIX getline(). It reads a line into allocated space, allocating more space until the data fits (or you run out of memory). It has at least one other major advantage over fgets() — it reports the number of characters in the line it read, which means it is not confused by null bytes ('\0', usually typed as Control-@) in the input. By contrast, you can't tell whether there was any data entered after the first null byte with fgets(); you have to assume that the input stopped at the first null byte.

Note that the simple loop shown in the question (while (getchar() != '\n');) becomes infinite if it encounters EOF before reading a newline.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    Bless your soul. That was the key, I was removing the newline character from the result of fgets and forgot I was doing that, so when I was attempting to look for it it always failed. Your code reminded me I was doing that and that I should check for the newline character in the result of fgets before removing it. – xjka Oct 03 '18 at 06:04
2
while ((getchar()) != '\n');

This will not always work...(but on the bright side, the cases in which it doesn't are just as portable as the cases in which it does). But if stdin has not been redirected, the terminal char of the user's input, unless a manual EOF, will usually be a newline. After you extract what you expect, assuming you don't expect the \n, you can drain what's there up until(and including) the '\n', and then iterate anew. As others have suggested, there are higher level interfaces to deal with this minutia more reliably than manual fringe case handling most of the time.

More Details on Challenge and Solutions This link contains the cardinal sin of "C\C++" in its heading, which doesn't exist as an entity. Rest assured, separate C examples are given, discrete from alternate C++ ones.

schulmaster
  • 413
  • 5
  • 16
  • 1
    To this I might add mention that in the question's case, since it sounds as if it is possible for users to enter less than the maximum number of characters as well, `fgets` will keep the newline in the read input in such cases (regarding not expecting the `\n`). Of course dealing with this oneself gets into the manual fringe case handling mentioned in this answer. – SevenStarConstellation Oct 03 '18 at 05:20