5

When I use the function fgets, the program skips the user input, effecting the rest of the program. An example program with this effect is:

#include <stdio.h>

int main() {
    char firstDigit[2];
    char secondDigit[2];

    printf("Enter your first digit: ");
    fgets(firstDigit, 1, stdin);
    printf("\nEnter your second digit: ");
    fgets(secondDigit, 1, stdin);
    printf("\n\nYour first digit is %s and your second digit is %s.\n", firstDigit, secondDigit);
}

I then thought that maybe the problem was that fgets might be writing the newline, so I changed the code to account for that:

#include <stdio.h>

int main() {
    char firstDigit[3];
    char secondDigit[3];

    printf("Enter your first digit: ");
    fgets(firstDigit, 2, stdin);
    printf("\nEnter your second digit: ");
    fgets(secondDigit, 2, stdin);
    printf("\n\nYour first digit is %c and your second digit is %c.\n", firstDigit[0], secondDigit[0]);
}

This time, the first input works properly, but the second input is skipped.

What am I doing incorrectly?

ericw31415
  • 378
  • 3
  • 17
  • @xing Doesn't the buffer need to be one character bigger to allow room for a `\0`? – ericw31415 Aug 11 '17 at 22:22
  • 1
    Your input buffers are far too small. Use `char firstLine[4096];` and `fgets(firstLine, sizeof(firstLine), stdin);` and then worry about what to do with any excess characters (ignoring them is probably appropriate). It allows the user to type leading blanks, too. – Jonathan Leffler Aug 12 '17 at 01:28
  • 1
    Note that you should specify the full size of the buffer (typically `sizeof(array)` — though there's a conversion from `size_t` to `int` there). You do not need to worry about 'off by one'; specify the full size because [`fgets()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fgets.html) won't overflow the buffer. Using a size of 1 only allows `fgets()` to store a null byte — no data at all. So it successfully reads nothing, rather fast, leaving the input stream unchanged. – Jonathan Leffler Aug 12 '17 at 01:32
  • @JonathanLeffler Is it common practice to make your buffer size 4096? – ericw31415 Aug 12 '17 at 01:34
  • 1
    POSIX says that the minimum value for the maximum size of a line `{LINE_MAX}` should be `{_POSIX2_LINE_MAX}` which is currently 2048. I double that for my purposes. See [``](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html). No, not everyone pays attention to this sort of detail, but it doesn't hurt unless you're on a very limited system. (When was the last time you were working on a machine with less than a gigabyte of main memory, which makes 4 KiB a minuscule fraction of the total?). I also do it for the shock value — to counter the problems that you have. – Jonathan Leffler Aug 12 '17 at 01:39
  • 1
    @JonathanLeffler I would usually calculate the minimum amount of space reasonably needed, and assign that as my buffer size. Memory allocated but not used is basically wasted, right? – ericw31415 Aug 12 '17 at 01:43
  • 1
    Yes, but 4 KiB is 0.0004% of 1 GiB, which is noise — actually, it is barely even that. If you're writing an application and the buffer will be permanently allocated in `main()`, then maybe you worry about it, but if it is in a function called from `main()`, then you won't need to worry about it — it will be released. And that way, you don't have to worry about dealing with the residue — someone who types `123456789012345678900987654321` when you wanted them to type `1`. People will abuse your program — allowing plenty of space for input makes it harder for them to crash your program. – Jonathan Leffler Aug 12 '17 at 01:48
  • Incidentally, on a Mac running macOS Sierra 10.12.6, using GCC 7.1.0, the following code produces 50 blank lines when given `/dev/null` as the input device: `#include ` — `int main(void) { char buffer[1]; int counter = 0; while (fgets(buffer, sizeof(buffer), stdin) != 0 && counter++ < 50) puts(buffer); return 0; }` – Jonathan Leffler Aug 12 '17 at 01:51
  • 1
    To add to the comments of @JonathanLeffler, it is likely that you will only need one large allocation for a user input buffer. As in the final example of my answer, you can use one input buffer, and store the relevant results in separate variables. – ad absurdum Aug 12 '17 at 01:54
  • @DavidBowling I still like your method of clearing the stream if it isn't empty. – ericw31415 Aug 12 '17 at 01:59

3 Answers3

4

char firstDigit[2] and char secondDigit[2] are not large enough to hold a digit, a newline character, and a null-terminator:

char firstDigit[3];
char secondDigit[3];

Then, the calls to fgets() need to specify the size of the buffer arrays:

fgets(firstDigit, sizeof firstDigit, stdin);
/* ... */
fgets(secondDigit, sizeof secondDigit, stdin);

When instead fgets(firstDigit, 2, stdin); is used, fgets() stores at most two characters, including the \0 character, in firstDigit[]. This means that the \n character is still in the input stream, and this interferes with the second call to fgets().

In answer to OP's comment, How would you remove the unread characters from the input stream?, a good start would be to use more generous allocations for firstDigit[] and secondDigit[]. For example, char firstDigit[100], or even char firstDigit[1000] will be large enough that any expected input will be taken in by fgets(), leaving no characters behind in the input stream. To be more certain that the input stream is empty, a portable solution is to use the idiomatic loop:

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

Note here that it is necessary to check for EOF, since getchar() may return this value if the user signals end-of-file from the keyboard, or if stdin has been redirected, or in the unlikely event of an input error. But also note that this loop should only be used if there is at least a \n character still in the input stream. Before attempting to clear the input stream with this method, the input buffer should be checked for a newline; if it is present in the buffer, the input stream is empty and the loop should not be executed. In the code below, strchr() is used to check for the newline character. This function returns a null pointer if the sought-for character is not found in the input string.

#include <stdio.h>
#include <string.h>           // for strchr()

int main(void)
{
    char firstDigit[3];       // more generous allocations would also be good
    char secondDigit[3];      // e.g., char firstDigit[1000];

    printf("Enter your first digit: ");
    fgets(firstDigit, sizeof firstDigit, stdin);

    /* Clear input stream if not empty */
    if (strchr(firstDigit, '\n') == NULL) {
        int c;
        while ((c = getchar()) != '\n' && c != EOF) {
            continue;
        }
    }

    putchar('\n');
    printf("Enter your second digit: ");
    fgets(secondDigit, sizeof secondDigit, stdin);

    /* Clear input stream if not empty */
    if (strchr(secondDigit, '\n') == NULL) {
        int c;
        while ((c = getchar()) != '\n' && c != EOF) {
            continue;
        }
    }

    puts("\n");
    printf("Your first digit is %c and your second digit is %c.\n",
           firstDigit[0], secondDigit[0]);

    return 0;
}

It may be even better to use a single buffer[] to store lines of input, and then to store individual characters in chars. You could also write a function to clear the input stream, instead of rewriting the same loop each time it is needed:

#include <stdio.h>
#include <string.h>           // for strchr()

void clear_stdin(void);

int main(void)
{
    char buffer[1000];
    char firstDigit;
    char secondDigit;

    printf("Enter your first digit: ");
    fgets(buffer, sizeof buffer, stdin);
    firstDigit = buffer[0];

    /* Clear input stream if not empty */
    if (strchr(buffer, '\n') == NULL) {
        clear_stdin();
    }

    putchar('\n');
    printf("Enter your second digit: ");
    fgets(buffer, sizeof buffer, stdin);
    secondDigit = buffer[0];

    /* Clear input stream if not empty */
    if (strchr(buffer, '\n') == NULL) {
        clear_stdin();
    }

    puts("\n");
    printf("Your first digit is %c and your second digit is %c.\n",
           firstDigit, secondDigit);

    return 0;
}

void clear_stdin(void)
{
    int c;
    while ((c = getchar()) != '\n' && c != EOF) {
        continue;
    }
}
ad absurdum
  • 15,925
  • 4
  • 28
  • 49
  • If I use this and type something like `123` for the first input, it uses `1` for the first digit, skips the second input and uses `3` for the second one? – ericw31415 Aug 11 '17 at 22:27
  • 1
    @ericw31415-- That is because `sizeof firstDigit[]` is 3, so at most 3 characters will be stored in `firstDigit[]` _including the null-terminator_. So, `'1'` and`'2'` are stored, then `'\0'`. This leaves the `'3'` and `'\n'` to be picked up by the second call to `fgets()`. – ad absurdum Aug 11 '17 at 22:32
  • 1
    @ericw31415 It should put `"12" in `firstDigit` and `"3\n"` in `secondDigit` – Barmar Aug 11 '17 at 22:32
  • How would you remove the unread characters from the input stream? – ericw31415 Aug 11 '17 at 23:02
  • @ericw31415-- I have added some comments and code to my answer to address your question about clearing the input stream. – ad absurdum Aug 11 '17 at 23:34
3

For the first case, fgets(firstDigit, 1, stdin); cannot read anything from the input because the buffer has a size of only 1 byte, and fgets() must store a null terminator into the destination.

For the second case: fgets(firstDigit, 2, stdin); reads 1 byte from stdin, the digit that you typed, and cannot read the newline because the destination array is already full, allowing for the null terminator. The second fgets() reads the pending newline from the first entry and returns immediately for the same reason, not letting you type the second input.

You must allow fgets() to read at least 2 bytes by providing a buffer size of at least 3:

#include <stdio.h>

int main(void) {
    char firstDigit[3];
    char secondDigit[3];

    printf("Enter your first digit: ");
    if (!fgets(firstDigit, sizeof firstDigit, stdin))
        return 1;
    printf("\nEnter your second digit: ");
    if (!fgets(secondDigit, sizeof secondDigit, stdin))
        return 1;
    printf("\n\nYour first digit is %s and your second digit is %s.\n",
           firstDigit, secondDigit);
    return 0;
}

Note that if you type more than a single character before the enter key, the program will still behave in an unexpected way.

chqrlie
  • 98,886
  • 10
  • 89
  • 149
0

This is a buffer problem. When you press enter, don't know why it is saved in the stdin buffer. After you perform an fgets(...) you must type fflush(stdin); on all circumstances.

Something like this:

printf("Enter your first digit: ");
fgets(firstDigit, 1, stdin);
fflush(stdin);
Jonathan Leffler
  • 666,971
  • 126
  • 813
  • 1,185
J. Francis
  • 155
  • 1
  • 8
  • `fflush(stdin)` is [undefined behavior](https://stackoverflow.com/questions/18170410/what-is-the-use-of-fflushstdin-in-c-programming) – yano Aug 11 '17 at 22:47
  • @yano Well, we can alternatively use "getchar();" to consume that newline. – J. Francis Aug 11 '17 at 22:53
  • See [Using `fflush(stdin)`](http://stackoverflow.com/questions/2979209/using-fflushstdin) — it is often not appropriate. – Jonathan Leffler Aug 12 '17 at 01:30
  • @JonathanLeffler as I told yano, we can alternatively use getchar(); to solve this. – J. Francis Aug 12 '17 at 01:41
  • 1
    Yes, so you commented. You haven't yet added that to your answer, though. Comments are ephemeral; answers aren't. And your answer still shows dubious practice. After reading either or both of the links, you could add some material to your answer — noting that if you're on Windows you might be OK using `fflush(stdin)`, but on Unix-like systems, you won't achieve anything useful. And then add the robust alternative. Note that if `fgets()` reads the newline, you won't want to gobble the input with `getchar()`, so you have to check what you got from `fgets()`. – Jonathan Leffler Aug 12 '17 at 01:42