(C)获得安全的int输入

时间:2018-11-22 20:49:57

标签: c

所以我责成自己写一个函数,

  1. 用安全值覆盖一个整数(如果 用户决定输入字符或绝对值更大的任何值 比(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不会超出其边界。我标记了 我无法用粗体弄清楚的部分,基本上就是 整个问题。

    也欢迎改进代码的建议。

2 个答案:

答案 0 :(得分:1)

  

欢迎提出改进代码的建议

代码在很多情况下失败


UB溢出

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

长行未消耗

当输入超过32个字符(包括'\n)时,剩下的输入将保留。

空字符输入

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

非ASCII输入

isdigit(input[i])input[i] < 0时的未定义行为。

假设范围

假定覆盖范围int,则使用2^31 - 1。 C要求int拥有一个 最小范围为[-32,767 ... 32,767]。

目标不明确

“如果输入超过(2 ^ 31-1)(表示用户输入8位或更多数字)”->如果输入为““ 0000000000000000000000000000000000001 \ n”,该怎么办? 35个零?它在范围内,但超过了8位数字,超过了33个字符缓冲区。

文件结束

如果输入已关闭,则

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


下面是一些未经测试的代码-稍后将进行测试。稍后,我将处理消息详细信息。可以肯定的是,比起简单地阅读一个“ int”可能需要的东西要多,但是如果您想要强大的代码,那就可以了。

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

我可以容忍前导和尾随空格。

strtol()很好,但是接下来需要首先读取整行。调用有效输入可以有多个前导空格或零。

不要溢出int算术- it is UB. Summing the value with negatives int`的范围要比正数范围大。

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

答案 1 :(得分:0)

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