Scanf() not able to detect wrong input

Viewed 128
int i, f;
f = scanf("%d", &i);

When I enter input as 3333333333333333333333 (greater than the capacity of int). Shouldn't the value of f be 0?

3 Answers

No, it can't be detected that way.

The below is not a portable solution, but it works in 12.1, 14.0 and 19.32. It may stop working in later releases.

You need to set errno = 0; first and then check it for range errors:

#include <errno.h>

// ...

    errno = 0;
    f = scanf("%d",&i);
    if(f == 1 && errno != ERANGE) {
        // success
    }

For portability, read this from an early draft of the C2x standard:

Unless assignment suppression was indicated by a *, the result of the conversion is placed in the object pointed to by the first argument following the format argument that has not already received a conversion result. If this object does not have an appropriate type, or if the result of the conversion cannot be represented in the object, the behavior is undefined.

A better (as in portable) option to detect this would be to read into a char[] buffer first and then use strtol() to convert it to a number. From the same standard draft:

The strtol, strtoll, strtoul, and strtoull functions return the converted value, if any. If no conversion could be performed, zero is returned. If the correct value is outside the range of representable values, LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, ULONG_MAX, or ULLONG_MAX is returned (according to the return type and sign of the value, if any), and the value of the macro ERANGE is stored in errno.

Here's a demonstrative program using strtol() (which converts to long):

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

// A wrapper around `strtol` to convert to `int`
int strtoi(const char *str, char **str_end, int base) {
    int errno_save = errno;
    errno = 0; // clear it from any previous error (must be done)
    long result = strtol(str, str_end, base);
    if(errno == ERANGE) return result == LONG_MAX ? INT_MAX : INT_MIN;
    if(result > INT_MAX || result < INT_MIN) {
        errno = ERANGE;
        return result > INT_MAX ? INT_MAX : INT_MIN;
    }
    // success or no conversion could be performed
    errno = errno_save;  // restore errno
    return (int)result;
}
#define Size(x) (sizeof (x) / sizeof *(x))

int main(void) {
    const char* strings[] = {
        "3333333333333333333333 foo",
        "2147483647 will probably succeed",
        "2147483648 will probably fail",
        "32767 guaranteed success",
        "32767xyz",
        "xyz",
        "123",
        ""
    };

    char *end; // this will point at where the conversion ended in the string

    for(unsigned si = 0; si < Size(strings); ++si) {

        printf("testing \"%s\"\n", strings[si]);
        errno = 0; // clear it from any previous error (must be done)
        int result = strtoi(strings[si], &end, 10);

        if(errno == ERANGE) {
            perror(" to big for an int");
        } else if(strings[si] == end) {
            fprintf(stderr, " no conversion could be done\n");
        } else if(*end != '\0' && !isspace((unsigned char)*end)) {
            fprintf(stderr, " conversion ok,"
                            " but followed by a rouge character\n");
        } else {
            printf(" success: %d rest=[%s]\n", result, end);
        }
    }
}

Possible output:

testing "3333333333333333333333 foo"
 to big for an int: Numerical result out of range
testing "2147483647 will probably succeed"
 success: 2147483647 rest=[ will probably succeed]
testing "2147483648 will probably fail"
 to big for an int: Numerical result out of range
testing "32767 guaranteed success"
 success: 32767 rest=[ guaranteed success]
testing "32767xyz"
 conversion ok, but followed by a rouge character
testing "xyz"
 no conversion could be done
testing "123"
 success: 123 rest=[]
testing ""
 no conversion could be done

Shouldnt the value of f be 0?

With standard C, no. With scanf("%d",&i), on int overflow, the result is undefined.

With scanf() in Unix (of which there are variations), I find no prevention of undefined behavior with overflow.

Best to ditch (not use) scanf() and use fgets() for all user input.


Code could try a textual width limit and a wider type:

intmax_t bigd;
//          vv --- width limit
if (scanf("%18jd",&bigd) == 1 && bigd >= INT_MIN && bigd <= INT_MAX) {
  d = (int) bigd;
} else {
  puts("Oops");
}

Yet that has trouble on novel implementations where int is as wide as intmax_t.


scanf() returns 0 when no int textual input found.

A key design element missing from OP's questions is what should happen to user input that exceeds the int range? Stop reading after the first `"333333333"?

What is best, depends on how OP wants to handle, in detail, error conditions - something not yet stated.

scanf("%d", &i) does not detect overflow, worse even, scanf() has undefined behavior if the number exceeds the range of the destination type: depending on the implementation, the value of i could be -434809515, -1, 0, INT_MAX or any value including a trap value with or without some undesirable side effects.

The proper way to check the input is to read it as a line in an array of char and to parse it with strtol():

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

int main() {
    char input[120];
    char ch;
    char *p;    
    long x;
    int i;

    printf("Enter an integer: ");
    if (!fgets(input, sizeof input, stdin)) {
        fprintf(stderr, "missing input\n");
        return 1;
    }
    errno = 0;
    x = strtol(input, &p, 0);
    if (p == input) {
        fprintf(stderr, "invalid input: %s", input);
        return 1;
    }
    if (x < INT_MIN || x > INT_MAX) {
        errno = ERANGE;
    }
    if (errno == ERANGE) {
        fprintf(stderr, "number too large: %s", input);
        return 1;
    }
    if (sscanf(p, " %c", &ch) == 1) {
        fprintf(stderr, "trailing characters present: %s", input);
        return 1;
    }
    i = (int)x;  // we know `x` is in the proper range for this conversion
    printf("The number is %d\n", i); 
    return 0;
}

You can encapsulate these tests in a getint() function:

#include <ctype.h>
#include <limits.h>
#include <stdio.h>

/* read an int from a standard stream:
   always update *res with the value read
   return 0 on success
   return -1 on out of range, value is clamped to INT_MIN or INT_MAX
   return -2 on non a number, value is 0
   only read characters as needed, like scanf
*/
int getint(FILE *fp, int *res) {
    int n = 0;
    int ret = 0;
    int c;
    while (isspace(c = getc(fp)))
        continue;
    if (c == '-') {
        c = getc(fp);
        if (!isdigit(c)) {
            ret = -2;
        } else {
            while (isdigit(c)) {
                int digit = '0' - c;
                if (n > INT_MIN / 10 || (n == INT_MIN / 10 && digit >= INT_MIN % 10)) {
                    n = n * 10 + digit;
                } else {
                    n = INT_MIN;
                    ret = -1;
                }
                c = getc(fp);
            }
        }
    } else {
        if (c == '+')
            c = getc(fp);
        if (!isdigit(c)) {
            ret = -2;
        } else {
            while (isdigit(c)) {
                int digit = c - '0';
                if (n < INT_MAX / 10 || (n == INT_MAX / 10 && digit <= INT_MAX % 10)) {
                    n = n * 10 + digit;
                } else {
                    n = INT_MAX;
                    ret = -1;
                }
                c = getc(fp);
            }
        }
    }
    if (c != EOF)
        ungetc(c, fp);
    *res = n;
    return ret;
}

int main() {
    int i, res;

    printf("Enter an integer: ");
    res = getint(stdin, &i);
    switch (res) {
    case 0:
        printf("The number is %d.", i);
        break;
    case -1:
        printf("Number out of range: %d, res=%d.\n", i, res);
        break;
    default:
        printf("Invalid or missing input, res=%d.\n", res);
        break;
    }
    return 0;
}
Related