1

I want to create a program that takes an integer as a variable. I want a way to deal with the error caused when a string or character is scanned on the buffer instead. My code would look something like this:

int input;

printf("Enter an integer: ");
scanf("%d", &input);

Just something simple like that, but I would like for the program to print out something like "Please enter a number" when someone enters "test" into the buffer, while still using scanf.

Rohan Bari
  • 6,523
  • 3
  • 9
  • 30
Belly
  • 29
  • 3
  • 4
    You use `fgets()` (and `strtol()`) for user input ... and remove `scanf()` from your toolbox. – pmg Feb 25 '21 at 18:02
  • Take a look at this answer which deals with error handling: https://stackoverflow.com/a/5087087 – Xertz Feb 25 '21 at 18:05
  • 2
    If you want to continue using `scanf`, at least you can test that the returned value of this function is equal to 1 here. – Damien Feb 25 '21 at 18:05
  • 2
    This problem is, for all intents and purposes, impossible to solve using `scanf`. If you want to write a program that behaves gracefully on bad input (which is a good and noble goal!), the only sane way to achieve it is to use techniques *other* than `scanf`. See [here](https://stackoverflow.com/questions/58403537/what-can-i-use-to-parse-input-instead-of-scanf) for a start. `scanf` simply does not deal well with bad input, and an attempt to add code to make it deal well runs into more and more difficulties, until it would have been much easier to start with something else. – Steve Summit Feb 25 '21 at 18:13
  • 2
    Why do you want to use `scanf`? Is it because your teacher told you you had to, or because you've gotten the impression it's the right way to do user input in C? If your teacher's insisting, I can't help you, but otherwise, please take the expert advice that you presumably came to SO to find, and learn that `scanf` is basically *terrible*. To do anything fancy, it is much, much easier to do it using something other than `scanf`, than using `scanf`. And, unfortunately, as far as `scanf` is concerned "prompt the user if they type something wrong" definitely does count as "something fancy". – Steve Summit Feb 25 '21 at 18:24
  • *I would like for the program to print out something like "Please enter a number" when someone enters "test" into the buffer, **while still using scanf**.* This sure seems apropos: [and I want a pony](https://www.urbandictionary.com/define.php?term=I%20want%20a%20pony) Seriously - `scanf()` is ill-suited for dealing with user input - historically, the name comes from "**scan f**ormated". In addition to user input not being "formatted", a failed `scanf()` conversion leaves the input stream in an unknown state. How can you recover without potentially losing data? It's all but impossible. – Andrew Henle Feb 25 '21 at 23:00

1 Answers1

2

Two things to remember about scanf and the %d conversion specifier:

  1. %d tells scanf to skip over any leading whitespace, then read any decimal digit characters up to the first non-decimal digit character;

  2. scanf returns the number of items successfully converted and assigned.

So, here's how scanf( "%d", &input ) will behave based on different input:

Input text        Value saved to input        Return value
----------        --------------------        ------------
   "  abc"                         n/a                   0
   "  12c"                          12                   1
   "  1b3"                           1                   1
   "  123"                         123                   1

In all cases, the %d conversion specifier tells scanf to skip over leading whitespace.

If the first non-whitespace character entered is a non-digit character, scanf will stop reading at that point, input will not be updated, and it will return 0.

If the first non-whitespace character is a decimal digit, scanf will read up to the first non-decimal digit character, then convert and assign the resulting digit string to input. Unfortunately, this won't help you catch cases like "12c" or "1b3" - as long as there's at least one decimal digit in the string, scanf( "%d", ... ) will treat it as a success and return 1.

So, if you want to validate that your user has input a valid decimal integer string, you have to do two things:

  1. You have to check the return value of scanf;
  2. You have to check the first character following the end of the input to see whether or not it's whitespace.

There are a couple of ways to accomplish the second part. You can read the character immediately following what should be the decimal integer input - if it's anything other than whitespace, then the input isn't valid:

char dummy;
int itemsRead = scanf( "%d%c", &input, &dummy );

if ( itemsRead == 0 || itemsRead == 2 && !isspace( dummy ) )
{
  fprintf( stderr, "Input is not valid, try again!\n" );
  while ( getchar() != '\n' )
    ; // empty loop
}

If itemsRead is 0, then the first input character was not a decimal digit character, and the input is not good.

If itemsRead is 2 and dummy is not a whitespace character, then we have a situation like "12c" or "1b3" and the input is not good.

If the input is not good, then write an error message and scrub the input stream of any remaining characters up to the next newline character using getchar.

If itemsRead is 1, then we saw EOF after nothing but decimal digit characters, so the input is good and there's nothing else to do. If itemsRead is 2 and dummy is whitespace, then the input is good and there's nothing else to do (I'm assuming you don't need to push whitespace back onto the input stream).

Alternately, you can read your input as text and use strtol to convert it to an integer. strtol will also set a pointer to point to the first character not converted:

#define MAX_INPUT_SIZE 11 // A 32-bit integer takes up to 10 decimal digits,
                          // plus a possible sign character. 

char buffer[MAX_INPUT_SIZE + 1];  // +1 for string terminator

if ( fgets( buffer, sizeof buffer, stdin ) )
{
  char *chk;
  int tmp = (int) strtol( buffer, &chk, 10 ); // convert decimal digit string
                                              // to an integer value
  if ( isspace( *chk ) || *chk == 0 )
  {
    input = tmp;
  }
  else
  {
    fprintf( stderr, "%s is not a valid input, try again\n", buffer );
  }
}
else
{
  // error on input
}
   

You'll notice I assigned the result of strtol to a temporary - it's a good idea to not update your target until after you've verified the input is good.

John Bode
  • 106,204
  • 16
  • 103
  • 178