【问题标题】:(C) Get safe int input(C) 获取安全的 int 输入
【发布时间】:2018-11-22 20:49:56
【问题描述】:

所以我要求自己编写一个函数,即:

  1. 用安全值覆盖 int(如果 用户决定输入 char-s 或任何更大的绝对值 比 (2^31-1)
  2. 如果输入超过 (2^31 - 1)(意味着如果用户输入 8 个或更多 数字) int 必须被上限值覆盖

代码如下:

void getSafeIntWithBoundaries(int *dest, int lo, int hi, const char *message);
bool anyChars(const char *input, int len);

int main() {
    int x;
    getSafeIntWithBoundaries(&x, 1, 10, "Enter an integer between 0 and 10.");
    printf("x = %d\n", x);
    return 0;
}

void getSafeIntWithBoundaries(int * dest, int lo, int hi, const char * message) {
    char input[33];
    while (1) {
        puts(message);
        fgets(input, 33, stdin);
        int len = strlen(input);
        if (input[len - 1] == '\n') { input[len - 1] = '\0'; }
        --len;
        if (bool reset = anyChars(input, len)) {
            puts("Try again.");
            continue;
        }
        else {
            int ret;
            if (strcmp("2147483648", input) < 0) {
                *dest = hi;
                return;
            }
            sscanf(input, "%d", &ret);
            ret = ret > hi ? hi : ret;
            ret = ret < lo ? lo : ret;
            *dest = ret;
            break;
        }
    }
}

bool anyChars(const char * input, int len) {
    for(int i = 0; i < len; i++) {
        if (!isdigit(input[i])) {
            return true;
        }
    }
    return false;
}

还有一些注意事项:

  • getSafeIntWithBoundaries(...) 我正在摆脱'\n',我是 将其更改为 '\0',分别减少 int len; 输入的长度。
  • anyChars() 检查输入是否包含任何非数字 char。如果 确实如此,然后用户必须重新输入。 其中一个问题是 但是,在失败的情况下,只需要打印消息 一次。如果我输入了很长的内容,将打印消息 多次。我不知道如何解决这个问题。
  • strcmp() 位检查用户输入的数字是否大于 (2^31 - 1)。如果用户有,那么 int 必须被覆盖 高值和功能需要结束。 但问题是,如果 用户输入一个很长的数字,目标 int 将是 被低边界覆盖。我不知道如何解决这个问题 要么。
  • 2 s 确保目标 int 不会超出其边界。我标记了 我用粗体无法弄清楚的部分,本质上就是 整个问题。

    也欢迎提出改进代码的建议。

【问题讨论】:

  • 你有什么问题?
  • 以粗体标记的内容。
  • 如果每一个都是一个问题,那么请专注于一个问题。但事实上,这些似乎都不是问题。整个帖子更多的是您想要匹配的要求列表。同样,您有什么与编程相关的特定问题?您想要更改代码的一件事是什么?您显示的代码如何不按预期运行?
  • 这似乎不是 C。
  • 好吧,@EOF 看起来怎么样?

标签: c


【解决方案1】:

欢迎提出改进代码的建议

代码失败很多情况


溢出 UB

当范围超过int时,sscanf(input, "%d", &amp;ret)是未定义的行为。

排长队不消耗

当输入超过 32 个字符(包括'\n)时,剩余的输入仍然存在。

空字符输入

空字符 '\0' 开头的输入会导致input[len - 1] 的未定义行为

非 ASCII 输入

isdigit(input[i])input[i] &lt; 0 时是未定义的行为。

假定远程

假设代码覆盖2^31 - 1 范围,代码使用int。 C 要求 int 有一个 [-32,767 ... 32,767] 的最小范围。

目标不明确

"如果输入超过 (2^31 - 1)(意味着如果用户输入 8 位或更多位)" --> 如果输入是 `"0000000000000000000000000000000000001\n" 怎么办? 35个零?它在范围内但超过 8 位并且超过 33 个字符缓冲区。

文件结束

puts("Try again."); 如果输入关闭则没有意义。我希望int getSafeIntWithBoundaries() 在成功时返回 1,在失败时返回 0,在文件结束/输入错误时返回 EOF


下面是一些未经测试的代码 - 稍后会测试。稍后我将处理 message 详细信息。这肯定比简单地读取一个 `int 所需的更多,但如果你想要健壮的代码,它是可行的。

要读取整个输入,必须读取到'\n'EOF

我会容忍前导和尾随空格。

strtol() 很好,但是需要先阅读整行。召回有效输入可以有许多前导空格或零。

不要溢出 intmath- it is UB. Summing the value with negativesint` 的范围大于正面。

C99 之前的 /,% 在余数不为零时具有实现定义的行为 - 所以我避免了这种情况。

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

#define INT_MIN_LS_DIGIT ((-(INT_MIN + 10)) % 10)
#define INT_MIN_DIV_10 ((INT_MIN + INT_MIN_LS_DIGIT)/10)

int getSafeIntWithBoundaries(int * dest, int lo, int hi, const char *message) {
  fputs(message, stdout);
  fflush(stdout);  // Insure data to sent out completely

  int ch;
  while (isspace((ch = fgetc(stdin))) && (ch != '\n')) {
    ;
  }
  bool positive = true;
  if (ch == '-' || ch == '+') {
    positive = ch == '+';
    ch = fgetc(stdin);
  }

  bool digit_found = false;
  bool overflow = false;

  int sum = 0;
  while (isdigit(ch)) {
    digit_found = true;
    int digit = ch = '0';
    // Detect possible overflow
    if (sum <= INT_MIN_DIV_10
        && (sum < INT_MIN_DIV_10 || digit > INT_MIN_LS_DIGIT)) {
      sum = INT_MIN;
      overflow = true;
    } else {
      sum = sum * 10 - digit;
    }
  }

  if (positive) {
    if (sum < -INT_MAX) {
      sum = INT_MAX;
      overflow = true;
    } else {
      sum = -sum;
    }
  }

  if (sum > hi) {
    sum = hi;
    overflow = true;
  }
  if (sum < lo) {
    sum = lo;
    overflow = true;
  }

  *dest = sum;

  while (isspace(ch) && ch != '\n') {
    ch = fgetc(stdin);
  }

  if (ch == EOF && iserror(stdin)) {
    return EOF; // Rare input error detected
  }

  if (!digit_found) {
    return 1; // or a "No digit found" error code
  }

  if (overflow) {
    errno = ERANGE;
    return 1; // or a "Overflow" error code
  }

   if (ch != '\n' && ch != EOF) {
    return 1; // or a "Extra trailing junk" error code
  }

  return 0;
}

【讨论】:

    【解决方案2】:

    strtol 可用于从字符串中解析整数。它提供溢出并且指向最后一个字符的指针允许测试有效的终止字符。这将范围设置为 0 和 INT_MAX,但可以使用从 INT_MIN 到 INT_MAX 的任何范围。终止字符为 nul,但可以是逗号、分号或任何适当的字符。

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <limits.h>
    
    //inputs
    // char *line : pointer to text to be parsed
    // char **next : pointer to pointer to allow modification of caller's pointer
    // char *term : pointer to characters to be considered terminators
    // int *value : pointer to int to allow modification of caller's int
    // int min : minimum value of range
    // int max : maximum value of range
    // returns : 0 failure or 1 success
    int get_int_range ( char *line, char **next, char *delim, int *value, int min, int max)
    {
        long int input = 0;
        char *end = NULL;//will point to end of parsed value
    
        if ( line == NULL) {
            return 0;
        }
        errno = 0;
        input = strtol ( line, &end, 10);//get the integer from the line. end will point to the end of the parsed value
        if ( end == line) {// nothing was parsed. no digits
            printf ( "input [%s] MUST be a number\n", line);
            return 0;// return failure
        }
        // *end is the character that end points to
        if ( *end != '\0' && !( delim && strchr ( delim, *end))) {// is *end '\0' or is *end in the set of term characters
            printf ( "problem with input: [%s] \n", line);
            return 0;
        }
        if ( ( errno == ERANGE && ( input == LONG_MAX || input == LONG_MIN))
        || ( errno != 0 && input == 0)){// parsing error from strtol
            perror ( "input");
            return 0;
        }
        if ( input < min || input > max) {// parsed value is outside of range
            printf ( "input out of range %d to %d\n", min, max);
            return 0;
        }
    
        if ( next != NULL) {// if next is NULL, caller did not want pointer to end of parsed value
            *next = end;// *next allows modification to caller's pointer
        }
        if ( value == NULL) {
            return 0;
        }
        *value = input;// *value allows modification to callers int
        return 1;// success
    }
    
    int main( int argc, char *argv[])
    {
        char line[900] = {'\0'};
        int valid = 0;
        int number = 0;
    
        do {
            printf ( "Enter number or enter quit\n");
            fgets ( line, sizeof ( line), stdin);//read a line
            if ( strcmp ( line, "quit\n") == 0) {
                return 1;// if quit is entered, exit the program
            }
            line[strcspn ( line, "\n")] = '\0';//remove trailing newline
            valid = get_int_range ( line, NULL, "", &number, 0, INT_MAX);// call to parse a value
        } while ( !valid);// on failure, keep looping the above
    
        printf ( "input is %d\n", number);
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-11
      • 1970-01-01
      • 1970-01-01
      • 2010-12-23
      • 1970-01-01
      相关资源
      最近更新 更多