1

As per the C FAQ: http://c-faq.com/stdio/scanfprobs.html

We should not use scanf for interactive input output, but instead we should resort to reading the whole line with fgets and then try to parse it with sscanf, prompting the user to type input again if sscanf returns parsing errors.

This, IIUC, would lead to code like this:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

int main()
{
        char inpbuff[5];
        signed char n;

        bool read_correctly = false;
        while(!read_correctly) {
            printf("Please enter an 8bit number: ");
            if(fgets(inpbuff, sizeof(inpbuff), stdin) == NULL)
                return EXIT_FAILURE;
            if(sscanf(inpbuff, "%hhd", &n) == 1)
                read_correctly = true;
        }

        printf("You have entered: %hhd\n", n);
        return EXIT_SUCCESS;
}

For me this approach creates problems if the user types a line that is longer than the size of the buffer provided for fgets. Even in the program above problems start to occur if the user types in asdf or asdf14.

In such a case we should, ideally, ignore all characters until we see a '\n', ignore this \n and only then again ask the user to provide their input. This would lead to an approach like this:

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

int main()
{
        char inpbuff[5];
        signed char n;

        bool read_correctly = false;
        while(!read_correctly) {
            printf("Please enter an 8bit number: ");
            switch(scanf("%hhd", &n)) {
            case 1:
                    read_correctly = true;
                    break;
            case 0: {
                    char sink;
                    do {
                            sink = fgetc(stdin);
                            if(sink == EOF)
                                    return EXIT_FAILURE;
                    } while(sink != '\n');
                    break;
            }
            default:
                    return EXIT_FAILURE;
            }
        }

        printf("You have entered: %hhd\n", n);
        return EXIT_SUCCESS;
}

Which I suppose must be suboptimal since it is contrary to what the C FAQ recommends! And I definitely do not consider myself wiser than the C FAQ authors.

So, how does a typical processing of interactive input/output in C look like?

7
  • Use malloc to create a buffer. If you don't see a \n in the fgets input buffer you can reallocate it and repeat until you do. Your phrase resort to reading the whole line with fgets suggests you are reluctant to go there. Commented Jul 10, 2017 at 17:45
  • Or, unless you are incredibly tight on memory, don't be mean, use char inpbuff[500]; Commented Jul 10, 2017 at 17:51
  • The FAQ doesn't say "Always use fgets()", merely that it's easier to use fgets() than scanf(). Personally, I almost always use fgets(), but in this case I'd probably just use scanf(). YMMV. Commented Jul 10, 2017 at 17:52
  • @WeatherVane I'm reluctant because (a) solution like the one You describe requires typing a loop which I’m not sure how can be any neater than what I wrote here, and (b) This means the program may crash if someone accidently or maliciously feeds it a very large input that exceeds the available RAM. Commented Jul 10, 2017 at 17:52
  • 1
    Note case 0: { char sink; should have been case 0: { int sink; to correctly distinguish an EOF from one of the 256 characters. Commented Jul 10, 2017 at 21:17

1 Answer 1

4

Your version misses a corner case - suppose I type in 1r4. Your scanf call will successfully convert and assign 1 to n, return 1 indicating success, and leave r4 in the input stream to foul up the next read. Ideally you'd like to reject 1r4 altogether.

That's why it's recommended to read the input as text, then process that buffer. If someone types in a line longer than the buffer is sized for, you handle it at the input stage by checking for a newline in the buffer - if it isn't there, reject the input as too large, then read and discard any additional characters until you see the newline.

while ( fgets( buffer, sizeof buffer, stdin ) )
{
  char *newline = strchr( buffer, '\n' );
  if ( !newline )
  {
    /**
     * input too large for buffer, throw away current input, read and 
     * discard additional characters until we see the newline or EOF
     */
    for ( int c = getchar(); c != EOF && c != '\n'; c = getchar() )
      ;
  }
  else
  {
    // process input buffer
  }
}

Yes, on a scale of 1 to pain-in-the-ass, interactive input in C defines pain-in-the-ass. You really do have to jump through all these hoops to guard against bad input.

You can bulletproof calls to scanf up to a point, but in the end it's honestly less of a hassle to do your own parsing.

Sign up to request clarification or add additional context in comments.

2 Comments

Nice. Agree that if user input is excessively long, it should be considered invalid in its entirety. Too many hackers trying to break code with strange input. This answer is better than endlessly reallocating a 2x buffer which gives hackers resource exploitation ability.
@chux: Yeah. Obviously this has some limitations; after all, we're relying on newlines to mark the ends of inputs, meaning we're expecting someone to hit Enter periodically. That's not great for input being redirected from a file that's had newlines stripped from it, or piped from another command, etc. This is one of those areas where C being a product of the early 1970s really stands out. Basically, you have to pick the edge conditions where you say, "nope, can't deal with this, fix your input."

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.