【问题标题】:Read from stdin with fgets, bug if input is greater than size使用 fgets 从标准输入读取,如果输入大于大小,则会出错
【发布时间】:2015-06-12 20:03:38
【问题描述】:

我正在创建一个带有提示的命令行应用程序。只要输入适合缓冲区,它就可以正常工作,但是当输入较大时,我会遇到一些奇怪的行为。下面是一个简化的最小示例,它具有相同的错误。

int main()
{
    for (;;) {
        const int size = 8;
        char str[size];

        printf("> ");
        fgets(str, size, stdin);

        toupper_str(str);
        printf("Result: %s\n", str);
    }
}

如果输入小于size,它可以正常工作。

> asdf
Result: ASDF
> 

当输入较大时,处理范围内的输入部分,并在下一次循环迭代中,立即从fgets 返回给定输入的其余部分。这导致输入的那部分也被处理和一些奇怪的输出。

> asdfghjk
Result: ASDFGHJ
> Result: K

> 

我可以通过将最后一个字符与换行符进行比较来查看输入是否大于或等于 size。 fgets 保留换行符,只要它适合。

fgets(str, size, stdin);
if (str[strlen(str) - 1] != '\n') {
    fprintf(stderr, "Input too long\n");
}

当检测到这一点时,如何阻止它在下一次迭代中读取剩余的过长输入?

我在这里看到过类似的问题,但没有人提出相同的问题。

【问题讨论】:

  • fgets 就是这样工作的。它确实丢弃输入的其余部分,您的代码应该考虑到这一点。如果末尾没有newline,则该行较长(文件的最后一行除外)。虽然如果您错过了 1 的长度,下一个输入可能包含 only newline.
  • @WeatherVane 问题是如何考虑它。这显然是fgets 的工作方式。
  • 您还没有说是要丢弃其余的太长输入还是考虑它。前者:如果fgets 字符串不以newline 结尾(当然在nul 之前)继续阅读getchar 直到newlineEOF。后者:如果输入不包含最终的newline,则使用分配的缓冲区和realloc。这里有一个不同的问题是一种使用该技术的方法。 stackoverflow.com/questions/28254245/…
  • 我认为您的问题最好表述为如何处理所有输入,而不是找到转储您无法处理的输入的方法:这会导致 GIGO。
  • @WeatherVane 正如您在提供的代码中看到的那样,缓冲区是在堆栈上分配的,因此不能是realloced。

标签: c


【解决方案1】:

如何阻止它在下一次迭代中读取其余太长的输入?

代码需要 1) 检测输入是否“太长” 2) 消耗额外的输入。

fgets() 不会溢出它的缓冲区。如果它确实填满了缓冲区,则缓冲区中的最后一个char'\0'。所以在阅读之前将其设置为非'\0'。然后代码知道整个缓冲区是否被填满。然后检查前面的char 是否为'\n'。如果不是行尾,则stdin中可能存在额外的char

char str[100];  // Insure buffer is at least size 2
for (;;) {
  str[sizeof str - 1] = `x`;
  if (fgets(str, size, stdin) == NULL) {
    // No more to read or IO error
    break;
  }
  int extra_data_found = 0;
  if (str[sizeof str - 1] == '\0' && str[sizeof str - 2] != '\n') {
    // Cope with potential extra data in `stdin`: read and toss
    int ch;
    while ((ch = fgetc(stdin)) != '\n' && ch != EOF) {
      extra_data_found = 1;
    }
  }
  // Use `str` as needed, noting if additional unsaved data found
  foo(str, extra_data_found);
}

注意:在文件错误时,fgets() 返回NULLstr 的内容未定义。

注意:代码可以使用strlen(str) == sizeof str - 1,而不是str[sizeof str - 1] == '\0'。如果fgets() 读取一个空字符'\0',这就会被愚弄。

角落案例:
1. 典型的str 最多可达 98 个char,然后是'\n''\0'last str 是 99 char 然后是 '\0' 可以吗?
2. 如果#1 没问题,那么典型的str 可能有99 个char,然后是'\0'

【讨论】:

    【解决方案2】:

    当输入太长时,需要在继续下一次循环迭代之前读取stdin上剩余的字符。

    if (fgets(str, size, stdin) == NULL) {
        if (feof(stdin)) {
            return 0;
        else {
            perror("Could not read from stdin");
            exit(1);
        }
    }
    else if (strchr(str, '\n') == NULL) {
        int c;
        while((c = getc(stdin)) != '\n' && c != EOF);
        fprintf(stderr, "Input too long\n");
    }
    

    如果您在 OS X 或 Linux 等 POSIX 系统上,则已经有 getline 函数从流中读取任意长度的换行符终止的字符串。您还可以在线找到该功能的许多免费/开源版本。

    【讨论】:

    • if (!feof(stdin)) ... "Could not read from stdin"... 是可疑的。通常,通过用户控制或通过重定向输入,stdin 将提供一段时间的输入,然后feof(stdin) 将为真。这不是“无法从标准输入读取”,更像是“无法读取”。
    • @chux fgets 可以在两种情况下返回NULL:如果遇到文件结尾或发生错误。如果它返回 NULL 并且它不是文件结尾,则它一定是一个错误,因此“无法从标准输入读取”。代码应该处理feof(stdin) 也为真的情况,我将编辑答案。
    • @jacwah 同意fgets() 在两种情况下返回NULL,但还有更多吗?考虑病理情况:stackoverflow.com/questions/23388620/…(不是因为这个你需要改变答案)
    • @chux 很好的发现!没想到这个。
    猜你喜欢
    • 1970-01-01
    • 2018-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多