Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scanf() not able to detect wrong input

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?

like image 739
koil Avatar asked Dec 12 '25 02:12

koil


2 Answers

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.

like image 135
chux - Reinstate Monica Avatar answered Dec 13 '25 16:12

chux - Reinstate Monica


No, it can't be detected that way.

The below is not a portable solution, but it works in gcc12.1, clang14.0 and msvc19.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
like image 30
Ted Lyngmo Avatar answered Dec 13 '25 14:12

Ted Lyngmo