Search code examples
cinputstdin

Program goes berserk after bad input (C)


I have a function that is supposed to get a number from stdin. It's supposed to check whether it's a valid number and optionally set it between a specific range. If the input is reasonably long (say 10 chars), then the function prints the error message and resets the loop, everything works as intended. However if I input something ridiculously long like:

1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

then something goes wrong. The loop keeps resetting but no matter what I type it doesn't accept it anymore, even if it would totally valid.

I guess this could be because stdin is somehow overflown? But isn't that the point of fgets() - it only reads a specific number of chars and discards everything else? How can I get around this without the use of exceptions?

The function in question:

int safeinp(int * num, const char *message, int low, int high)
{
    long a;
    char buf[11]; // 9 digits for the number + "\n\0"
    int success; // flag for successful conversion

    do
    {
        puts(message);
        if (!fgets(buf, 11, stdin))
        {
            fprintf(stderr, "Unagle to obtain input.\n");
            return 1;
        }

        // have some input, convert it to integer:
        char *endptr;

        a = strtol(buf, &endptr, 12);
        if (errno == ERANGE)
        {
//this if() right here is what gets executed endlessly if the input is bad
                fprintf(stderr, "Invalid number.\n");
                success = 0;
            }
            else if (endptr == buf)
            {
                fprintf(stderr, "Invalid input.\n");
                success = 0;
            }
            else if (*endptr && *endptr != '\n')
            {
                fprintf(stderr, "Conversion error.\n");
                success = 0;
            }
            else
            {
                success = 1;
                if (low != high) {
                    a = (a < low) ? fprintf(stderr, "Input has been adjusted to fit the bounds.\n"), low : a;
                    a = (a > high) ? fprintf(stderr, "Input has been adjusted to fit the bounds.\n"), high : a;
                }
                *num = a;
            }
        } while (!success); 
        return success;
    }

Problem occurs in Visual Studio 2017 for Windows 10.


Solution

  • No, fgets() does not empty the "buffer". If you enter a long string, your code will read 10 characters at a time until it reaches the end-of-line of your input. The next loop will wait again.

    Stripped down example:

    #include <stdlib.h>
    #include <stdio.h>
    
    int main(int argc, char **argv)
    {
      char buf[11];
    
      do {
        puts("TEST");
        fflush(stdout);
        if (!fgets(buf, sizeof(buf), stdin)) {
          fprintf(stderr, "Unagle to obtain input.\n");
          return 1;
        }
    
        printf("input: %s\n", buf);
      } while (1);
    
      return(0);
    }
    

    Test run:

    $ gcc -Wall -o dummy dummy.c
    $ ./dummy 
    TEST
    123456789012345678901234567890
    input: 1234567890
    TEST
    input: 1234567890
    TEST
    input: 1234567890
    TEST
    input: 
    
    TEST
    ^C
    $
    

    UPDATE: proposal which tries to eat all remaining characters until newline:

        /* replacement for fgets(buf, sizeof(buf), stdin) */
        char *p = buf;
        char c;
        unsigned left = sizeof(buf) - 1;
        while ((left-- > 0) && ((c = fgetc(stdin)) != '\n')) {
          if (feof(stdin)) {
            return(1);
          }
          *p++ = c;
        }
        *p++ = '\0';
    
        /* eat the rest until newline */
        while (c != '\n') {
          c = fgetc(stdin);
          if (feof(stdin)) {
            return(1);
          }
        }
    

    New test run:

    $ gcc -Wall -o dummy dummy.c
    $ ./dummy 
    TEST
    123456789012345678901234567890
    input: 1234567890
    TEST
    1
    input: 1
    TEST
    1234567890
    input: 1234567890
    TEST
    ^C