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
?
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
?
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
, andstrtoull
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
, orULLONG_MAX
is returned (according to the return type and sign of the value, if any), and the value of the macroERANGE
is stored inerrno
.
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;
}