c-Scanf()无法检测到错误的输入


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

当我输入输入为3333333333333333333333(大于int的容量)时。f的值不应该是0吗?

f的值不应该是0吗?

对于标准C,否。对于scanf("%d",&i),在int溢出时,结果为未定义

使用Unix中的scanf()(有一些变体),我发现无法防止溢出的未定义行为。

对于所有用户输入,最好放弃(不使用)scanf()并使用fgets()


代码可以尝试文本宽度限制和更宽的类型:

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

然而,这在intintmax_t一样宽的新颖实现中存在问题。


scanf()在未找到int文本输入时返回0。

OP问题中缺少的一个关键设计元素是,超过int范围的用户输入应该发生什么?在第一个"之后停止阅读;333333333";?

什么是最好,取决于OP希望如何详细处理错误条件——这一点尚未说明。

不,不能通过这种方式检测到。

以下不是一个可移植的解决方案,但它适用于gcc12.1、clang14.0和msvc19.32。它可能在以后的版本中停止工作。

您需要先设置errno = 0;,然后检查其范围错误:

#include <errno.h>
// ...
errno = 0;
f = scanf("%d",&i);
if(f == 1 && errno != ERANGE) {
// success
}

为了便于移植,请阅读C2x标准的早期草案:

除非*指示了赋值抑制,否则转换结果将放置在尚未接收到转换结果。如果此对象没有合适的类型,或者如果转换的结果无法在对象中表示,则行为未定义

一个更好的检测方法(如portable)是先读取char[]缓冲区,然后使用strtol()将其转换为数字。来自同一标准草案:

strtolstrtollstrtoulstrtoull函数返回转换后的值(如果有的话)。如果无法执行转换,则返回零。如果正确的值在可表示值的范围之外,则返回LONG_MINLONG_MAXLLONG_MINLLONG_MAXULONG_MAXULLONG_MAX(根据值的返回类型和符号,如果有的话),并且宏ERANGE的值存储在errno中。

下面是一个使用strtol()(转换为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 donen");
} else if(*end != '' && !isspace((unsigned char)*end)) {
fprintf(stderr, " conversion ok,"
" but followed by a rouge charactern");
} else {
printf(" success: %d rest=[%s]n", result, end);
}
}
}

可能输出:

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

scanf("%d", &i)不会检测到溢出,更糟糕的是,如果数量超过目标类型的范围,scanf()会有未定义的行为:根据实现的不同,i的值可能是-434809515-10INT_MAX或任何值,包括陷阱值,有或没有一些不希望的副作用。

检查输入的正确方法是将其读取为char数组中的一行,并使用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 inputn");
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 %dn", i); 
return 0;
}

您可以将这些测试封装在getint()函数中:

#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;
}

最新更新