12

I want to read a int from stdin but I want to validate if the user exceeds the int max value. How can I do it?

int n;
scanf("%d", &n);

scanf reads the decimal input and stores in the int, causing overflow. How can I check and avoid this?

Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
António Silva
  • 121
  • 1
  • 1
  • 3

5 Answers5

17

The only way to convert a string representation of a number to the actual value and to watch for overflow is to use functions from strto.. group. In your case you need to read in a string representation of the number and then convert it using strtol function.

Beware of responses that suggest using atoi or sscanf to perform the final conversion. None of these functions protect from overflow.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Can you show a complete example on how to convert to a long and check the "overflow" error? – AlfaTeK Dec 02 '09 at 22:16
  • There's not much to show. In case of overflow `strtol` sets `errno` to `ERANGE`. Just check the value of `errno` to catch overflow. – AnT stands with Russia Dec 02 '09 at 22:56
  • And that is Ansi C compatible (C89)? – AlfaTeK Dec 03 '09 at 01:05
  • Yes, this is now `strtol` (and the rest of `strto...` functions) is described in C89/90. – AnT stands with Russia Dec 03 '09 at 01:49
  • The function `strtol` will check whether the range fits within a `long`. However, OP is asking about the range of an `int`. Therefore, after performing the range check for `long`, you must additionally compare the converted value with [`INT_MIN`](https://en.cppreference.com/w/cpp/types/climits#Limits_of_integer_types) and `INT_MAX`, as I have done in my answer. – Andreas Wenzel Sep 26 '21 at 08:47
3

Another way is to set max digits to parse.

For example:

  int n;
  scanf("%5d", &n); // read at most 5 digits
  printf("%i\n", n);
Nick Dandoulakis
  • 42,588
  • 16
  • 104
  • 136
2

According to §7.21.6.2 ¶10 of the ISO C11 standard, if the result of a conversion performed by the function scanf cannot be represented, then the behavior is undefined. Therefore, this function cannot be reliably used for the purpose of input validation.

However, when converting a string to an integer using the function strtol, if a range error occurs, the behavior is well-defined. In this case errno will be set to ERANGE. This will tell you if the number the user entered is out of range of a long. If you want to determine whether it is out of range of an int, then you must additionally verify that the number is larger than or equal to INT_MIN and smaller than or equal to INT_MAX. Note that you must #include <limits.h> to use these constants.

Therefore, I recommend the following code to read a number from the user and to validate it:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

int main( void )
{
    char buffer[1024], *p;
    long num_long;
    int  num_int;

    //prompt user for input
    printf( "Enter a number: " );

    //get one line of input from input stream
    if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
    {
        fprintf( stderr, "Unrecoverable error reading from input!\n" );
        exit( EXIT_FAILURE );
    }

    //make sure that entire line was read in (i.e. that
    //the buffer was not too small)
    if ( strchr( buffer, '\n' ) == NULL )
    {
        printf( "Line input was too long!\n" );
        exit( EXIT_FAILURE );
    }

    //attempt to convert string to number
    errno = 0;
    num_long = strtol( buffer, &p, 10 );
    if ( p == buffer )
    {
        printf( "Error converting string to number\n" );
        exit( EXIT_FAILURE );
    }

    //make sure that no range error occurred
    if ( errno == ERANGE || num_long < INT_MIN || num_long > INT_MAX )
    {
        printf( "Range error!\n" );
        exit( EXIT_FAILURE );
    }

    //range is ok, so we can convert to int
    num_int = (int)num_long;

    //make sure that remainder of line contains only whitespace,
    //so that input such as "6sdfh4q" gets rejected
    for ( ; *p != '\0'; p++ )
    {
        if ( !isspace( (unsigned char)*p ) )
        {
            printf( "Unexpected input encountered!\n" );
            exit( EXIT_FAILURE );
        }
    }

    //number was successfully converted, so we print it
    printf( "You entered the following valid number: %d\n", num_int );
}

Example output of this program when entering a valid number:

Enter a number: 65
You entered the following valid number: 65

Example output of this program when entering invalid input:

Enter a number: 6sdfh4q
Unexpected input encountered!

Example output of this program when entering out of range input:

Enter a number: 3000000000
Range error!
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
0

Two methods come to mind.

The first is useful if you know the input is integer-like only input, but suffers from "slightly" exceeding integer range:

long x;
if (1 != scanf("%ld", &x))
         error "not a number or other i/o error"
else
if (x < -MAXINT ||  x > MAXINT)
        error "exceeds integer range"
else    printf "it is an integer";

This has a lot of dependency on the compiler and platform. Note that the C language guarantees only that long is greater than or equal to the size of int. If this is run on a platform where long and int are the same size, it is a useless approach.

The second approach is to read the input as a string and parse straightforwardly:

char buf [1000];
if (1 != scanf ("%1000s", buf))
        error "i/o error";
size_t len = strspn (buf, "0123456789+-");
if (len != strlen (buf))
        error "contains invalid characters";
long  number = 0;
int   sign = 1;
for (char *bp = buf;  *bp;  ++bp)
{
          if (*bp == '-')
          {
               sign = -sign;
               continue;
          }
          if (*bp == '+')
               continue;
          number = number * 10  +  *bp - '0';
          if (number > MAXINT)
               error "number too large";
}
if (sign < 0)
         number = -number;

This code has several weaknesses: it depends on long being larger than int; it allows plus and minus to appear anywhere in the string. It could be easily extended to allow other than base ten numbers.

A possible third approach might be to input a string check the character set and use a double conversion to range check.

wallyk
  • 56,922
  • 16
  • 83
  • 148
-1

Read it into a string and check the length, then call either atol() or sscanf() on the string to convert it into a int.

Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
Martin Beckett
  • 94,801
  • 28
  • 188
  • 263
  • 2
    Not `atoi` and not `sscanf`. Neither of these protect from overflow. "Checking the length" cannot be used to prevent overflow for obvious reasons. – AnT stands with Russia Nov 07 '09 at 20:42
  • you can assume though that if the entered number is 10 digits long it isn't gong to fit into an int. – Martin Beckett Nov 08 '09 at 01:56
  • 2
    `2000000000` is 10 digits long and it fits into a 32-bit int without a problem. – AnT stands with Russia Nov 08 '09 at 02:13
  • Just in case it's not obvious to you why checking the length does not work, consider an environment where `INT_MAX == 2147483647`, which is 10 digits. Now `3000000000` is also 10 digits, but exceeds INT_MAX, leading to overflow. – syockit May 14 '18 at 00:32