scanf()无法检测到错误输入

发布于 2025-02-11 18:48:23 字数 183 浏览 2 评论 0 原文

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

当我输入以 33333333333333333333333 (大于 int 的容量)时,输入输入。 f 的值不应该为 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

当梦初醒 2025-02-18 18:48:23

f的值不应该为0?

使用标准C,没有。使用 scanf(“%d”,& i) int 溢出,结果为 undfeined

使用 scanf() in unix (哪个(哪个)有变化),我发现没有预防溢出的不确定行为。

最好抛弃(不使用) 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");
}

然而,在 int 的新颖实现上却有困难,宽度与 intmax_t 一样宽。


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

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

什么是最佳,取决于OP想要如何详细处理错误条件 - 尚未说明的事情。

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.

挽手叙旧 2025-02-18 18:48:23

不,无法以这种方式检测到。

以下不是便携式解决方案,但它在 gcc 12.1, clang 14.0和 msvc 19.32。它可能停止在以后的版本中工作。

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

#include <errno.h>

// ...

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

对于可移植性,请从 C2X标准的早期草稿

除非*表示抑制分配抑制
转换结果。如果此对象没有适当的类型,或者如果转换的结果无法在对象中表示,则行为是未定义的

一个更好的(如 portable 中)检测到的选项是首先阅读到 char [] buffer,然后使用 strtol() 将其转换为数字。从相同的标准草稿中:

strtol strtoll strtoul strtoull 功能函数返回转换的值(如果有)。如果无法执行转换,则返回零。如果正确的值超出了表示值的范围,则 long_min long_max llong_min llong_max ulong_max ullong_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 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);
        }
    }
}

可能的输出:

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

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
羁〃客ぐ 2025-02-18 18:48:23

scanf(“%d”,&amp; i)不会检测到溢出,甚至更糟, scanf()如果数字超过目标类型的范围:根据实现的不同, i 的值可以是 -434809515 -1 0 0 , int_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 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;
}

您可以将这些测试封装在<<<代码> 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;
}

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文