【问题标题】:How do you make a safe input of an int with retry logic?如何使用重试逻辑安全输入 int?
【发布时间】:2021-12-17 22:40:01
【问题描述】:

我正在尝试写入一个程序,该程序从终端接收一个整数并安全地检查它是否真的是一个 int 以及它是否等于或介于 0 和 5 之间。代码还应该具有重试逻辑。

这是我写的代码:

#include <stdio.h>
#include <stdlib.h>


int main(){
    int input;
    char i[3];
    char *p;

    while(fgets(i, (sizeof(i)),stdin)){
        input=strtol(i,&p,10);
        if(input<0 || input>5 || p==i || (*p)!='\n'){
            printf("please enter a integer larger or equal to 0 or smaller or equal to 5\n");   
        }
        else{
            printf("%d",input);
            break;
        }
    }
}

我遇到的问题是,如果我输入的字符多于fgets() 读取的字符,例如“aaaaa”,我会得到输出:

please enter a integer larger or equal to 0 or smaller or equal to 5
please enter a integer larger or equal to 0 or smaller or equal to 5
please enter a integer larger or equal to 0 or smaller or equal to 5 

我假设问题是fgets 未读取的字符留在缓冲区中,并在触发 if 语句并因此重复输出的后续迭代中被读取。我假设解决方案是以某种方式清除缓冲区,但从我读过的fflush() 来看是不允许的。有没有一种简单的方法来清除缓冲区?如果这确实是问题的问题,你会怎么做?

【问题讨论】:

  • "有没有简单的方法来清除缓冲区?" --> 是的,scanf("%*[^\n]"); scanf("%1[\n]"); 或通常的 fgetc() while 循环。棘手的部分是要知道缓冲区是否需要清除,因为这取决于您需要代码的迂腐程度。例如fgets() 在输入包含 空字符 或输入错误时出现问题。
  • 如果输入字符串不包含换行符,还有更多等待读取。 fgets 最多读取并包括下一个换行符,取决于缓冲区大小和 EOF。
  • 如果你只取一个数字,那么你根本不需要strtolfgets。使用fgetc 读取一个字符并将其与'0' 进行比较。 (例如,c = getchar(); input = c - '0'; 然后使用 while( (c = getchar()) != EOF &amp;&amp; c != '\n' ); 消耗该行的其余部分
  • 你可能想看看我在this answer to another question末尾写的函数get_int_from_user。我相信这个函数可以满足你的所有需求,除了检查输入是否在05 之间,你必须自己做。
  • 永远不要吝啬你的输入缓冲区。特别是如果您试图在面对输入错误时保持稳健。您正在尝试处理用户可以键入 anything 的情况。因此,您必须假设用户可能键入了多个字符。因此,让您的输入缓冲区i[] 比您希望用户键入的任何合理长的行都大得多。我至少会声明char i[512];,或者这些天可能是char i[1024];。内存很便宜!

标签: c


【解决方案1】:

简单的方法从更大的缓冲区开始。

#define BUF_N 100
char buf[BUF_N];

然后是一个不那么简单的方法来测试各种麻烦。我怀疑存在简化,但至少这个处理了许多角落并且是说明性的。

为简单起见,如果输入行过长,我们就说输入行不好,不管读取的文本如何。

  #define BUF_N 100
  char buf[BUF_N];

  while (fgets(buf, sizeof buf, stdin)) {
    // If entire buffer filled, the line may be longer, read it.
    if (strlen(buf) == BUF_N - 1 && buf[BUF_N - 2] != '\n') {
      printf("Excessive input\n");
      // Consume rest of the line 
      int ch;
      while ((ch = fgetc(stdin)) != '\n') {
        if (ch == EOF) {
          return EOF;
        }
      }
      continue;
    }

    char *p;
    long input = strtol(buf, &p, 10); // use long

    if (buf == p) {
      printf("Non-numeric input\n");
      continue;
    }

    if (input < 0 || input > 5) {
      printf("Out of range %ld\n", input);
      continue;
    }

    // Look for trailing junk
    while (isspace(*(unsigned char* )p)) {
      p++;
    }
    if (*p) {
      printf("Trailing junk\n");
      continue;
    }

    return (int) input;
  }
  return EOF;
}

上面对读取空字符不是那么宽容。

【讨论】:

  • 我喜欢您正在检查“尾随垃圾”。这是我第一次看到别人这样做。我看到的大多数代码都简单地接受诸如6sdfj23jlj 之类的输入作为数字6 的有效输入,尽管它可能应该被拒绝。
  • @AndreasWenzel 也许更简单的测试是if (sscanf(buf, "%4d %c", &amp;int_input, &amp;ch) == 1 &amp;&amp; int_input &gt;= 0 &amp;&amp; int_input &lt;= 5)
  • @AndreasWenzel 对我来说,学究式的测试也会处理读取 null 字符fgets() 无法报告。
  • 是的,sscanf if 条件可能会很好地工作,除了如果转换结果不能表示为 int 时行为未定义这一事实。这就是为什么我更喜欢函数strtol,因为该函数的行为在所有情况下都是明确定义的。
  • @AndreasWenzel 所有 4 个字符的数字输入都可以表示为 int,因此是 "%4d"。确实它不会处理"000001"
【解决方案2】:

使用3 的缓冲区大小调用函数fgets 将最多读取一个字符,除了换行符和终止空字符。如果输入应该是介于05 之间的数字,那么这种行为是可以接受的,但是您应该知道您的程序将无法读取诸如05 之类的输入,即使它满足范围要求。

如果你想允许输入如05,那么你应该增加缓冲区大小。

您可以轻松检测用户在行中输入的字符是否多于缓冲区。由于函数fgets 也读取换行符并将其存储到缓冲区中,因此您可以检查此换行符是否存在,例如使用函数strchr。如果您确定fgets 没有读取换行符,则这意味着输入太长而无法放入缓冲区。在这种情况下,您可能希望拒绝输入并丢弃输入的其余部分,以便下次调用 fgets 时不会读取它。这就是您的程序中发生的情况,导致同一行被多次处理。

丢弃剩余输入的一种方法是调用getchar,它一次读取一个字符。您应该重复此操作,直到遇到换行符,这意味着已到达行尾。

这就是代码的样子:

while( fgets( i, sizeof i, stdin ) != NULL )
{
    //verify that entire line of input was read
    if ( strchr( i, '\n' ) == NULL )
    {
        int c;

        //verify that stream state is as expected
        if ( feof(stdin) || ferror(stdin) )
        {
            fprintf( stderr, "unexpected input error!\n" );
            exit( EXIT_FAILURE );
        }

        //print error message
        printf( "Line input was too long to fit into buffer!\n" );

        //discard remainder of line
        do
        {
            c = getchar();

            if ( c == EOF )
            {
                fprintf( stderr, "unexpected input error!\n" );
                exit( EXIT_FAILURE );
            }

        } while ( c != '\n' );

        continue;
    }

    //input was successful, so we can now process it with strtol
}

请注意,您必须另外 #include &lt;string.h&gt; 才能使用函数 strchr

【讨论】:

  • if ( strchr( i, '\n' ) == NULL ) 是一个问题,当输入的最后一行是 "123" 而没有 '\n'as “那么这意味着输入太长而无法放入缓冲区。”不是真的。没有'\n' 的最后一行是有争议的角落。
  • @chux:OP 似乎正在处理用户输入。因此,引入文件结尾检查似乎违反直觉,并且可能会使 OP 感到困惑,即使可以在终端中输入文件结尾也是如此。但是,我确实同意,在处理可能来自其他来源的输入时,检查前面没有换行符的文件结尾很重要,即使 POSIX defines a line as always ending with a newline character
  • 是的,读取 line 会出现很多问题。
  • @chux:实际上,通过在do 循环中检查EOF,我的代码有点不一致,但在得出结论之前没有检查错误或文件结尾输入太长而无法放入缓冲区。因此,我现在决定添加这些检查并相应地修改了我的代码。
猜你喜欢
  • 2020-06-11
  • 2017-03-21
  • 1970-01-01
  • 1970-01-01
  • 2022-09-25
  • 2013-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多