【问题标题】:How to read input of unknown length using fgets如何使用 fgets 读取未知长度的输入
【发布时间】:2015-02-04 06:28:07
【问题描述】:

我应该如何使用fgets() 读取长输入,我不太明白。

这是我写的

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

int main()
{
    char buffer[10];
    char *input;
    while (fgets(buffer,10,stdin)){
        input = malloc(strlen(buffer)*sizeof(char));
        strcpy(input,buffer);
    }
    printf("%s [%d]",input, (int)strlen(input));
    free(input);
    return 0;
}

【问题讨论】:

  • realloc。并考虑每次加倍分配的策略,以及直接读入缓冲区。顺便说一句:size_t 的 printf 格式是 %zu
  • 如果有,请考虑使用getline 而不是fgets
  • 我知道 getline,但我必须使用 fgets
  • 我看到了它们,但我不明白 fgets 是如何工作的,当输入比我指定的缓冲区长时会发生什么,它会等待我分配内存并将其存储在那里吗?
  • 您的代码在 malloc() 中有一个经典的非一错误。使用strlen(str) 几乎总是错误的;它应该几乎总是strlen(str)+1。您的循环也会严重泄漏内存;您在每次迭代时覆盖存储在 input 中的前一个指针。

标签: c string input string-length


【解决方案1】:

一个更好的方法是使用一个输入机制来分配给你,比如getline(甚至scanf)。 (注意: scanf 不会在所有编译器中分配。它会在 gcc/Linux 中分配,但不会在 Windows/Codeblocks/gcc 中分配)

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

int main()
{
    char *input;
    scanf ("%m[^\n]%*c", &input);
    printf("\n %s [%d]\n\n",input, (int)strlen(input));
    free(input);
    return 0;
}

输出:

$ ./bin/scanfinput
This is my longer string.

 This is my longer string. [25]

getline 示例

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

int main()
{
    char *input = NULL;     /* input buffer, NULL forces getline to allocate */
    size_t n = 0;           /* maximum characters to read (0 - no limit      */
    ssize_t nchr = 0;       /* number of characters actually read            */

    if ((nchr = getline (&input, &n, stdin)) != -1)
        input[--nchr] = 0;  /* strip newline */

    printf ("\n %s [%zd]\n\n", input, nchr);
    free(input);

    return 0;
}

【讨论】:

  • 谢谢,这给了我警告,未知格式 %m,我正在使用 c90
  • 如果你在Windows中工作,scanf不会分配如果使用codeblocks等,请使用getline。我将仅针对 Linux 进行更新。我正在使用带有 gnu 扩展的gcc/C89
  • 摆脱代码块:) 我有其他人报告说,如果使用codeblocksscanf 将不会分配。这一定是个限制。​​
  • Code::Blocks 与编译无关。它只是 IDE
  • 那里的底线是什么?我有另一个报告,在带有代码块的 Windows 上使用 gcc 设置,scanf 无法分配。我没有也没有办法测试。
【解决方案2】:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char buffer[10];
    char *input = 0;
    size_t cur_len = 0;
    while (fgets(buffer, sizeof(buffer), stdin) != 0)
    {
        size_t buf_len = strlen(buffer);
        char *extra = realloc(input, buf_len + cur_len + 1);
        if (extra == 0)
            break;
        input = extra;
        strcpy(input + cur_len, buffer);
        cur_len += buf_len;
    }
    printf("%s [%d]", input, (int)strlen(input));
    free(input);
    return 0;
}

这是关于为您提供完整输入行的最小更改集。这一次将空间最多增加 9 个字节;这不是最好的方法,但是要以更好的方式进行额外的簿记(将分配的空间加倍,并记录分配的空间与正在使用的空间)。注意cur_len记录了input指向的空间中字符串的长度,不包括终端null。另请注意,使用extra 可避免分配失败时发生内存泄漏。

strcpy() 操作可以合法地替换为memmove(input + cur_len, buffer, buf_len + 1)(在这种情况下,您可以使用memcpy() 代替memmove(),但它并不总是有效,而memmove() 总是有效,所以使用memmove()更可靠)。


使用加倍长度 - cur_max 变量记录分配了多少空间,cur_len 记录了正在使用的空间量。

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

int main(void)
{
    char buffer[10];
    char *input = 0;
    size_t cur_len = 0;
    size_t cur_max = 0;
    while (fgets(buffer, sizeof(buffer), stdin) != 0)
    {
        size_t buf_len = strlen(buffer);
        if (cur_len + buf_len + 1 > cur_max)
        {
            size_t new_len = cur_max * 2 + 1;
            if (buf_len + 1 > new_len)
                new_len = buf_len + 1;
            char *extra = realloc(input, new_len);
            if (extra == 0)
                break;
            input = extra;
            cur_max = new_len;
        }
        strcpy(input + cur_len, buffer);
        cur_len += buf_len;
    }
    printf("%s [%d]", input, (int)strlen(input));
    free(input);
    return 0;
}

【讨论】:

  • 谢谢,所以如果我想阅读通常很长的行,我必须使用 realloc 将大小调整为以前大小的 2 倍?
  • 否;最简单的事情是使用char buffer[4096]; 并继续生活。只有 JSON 的行数比这更长,然后您可以在一行中读取整个文件。 (我夸大了,但没有那么夸张。)如果做不到这一点,想法是您第一次分配 10 个字节,然后重新分配 20,然后重新分配 40(但将 2 个单位的 10 读入额外空间),然后80,然后 160,然后 320... 如果您不打算在函数退出之前释放空间并且有超过 64 个字节未使用,您可以使用realloc() 将分配缩小到所需的大小。
  • 除非你在一个非常小的机器上,否则 4 KiB 的缓冲区不会让你感到悲伤。如果你在这么小的机器上,你可能根本没有使用malloc() - 或者阅读这么长的行。
  • 感谢您的回答, if (extra == 0) break;这在你的修改中,是0代表'NULL'吗?
  • NULL 是 0 的另一个名称 - 或多或少。有一些复杂性,但两者都是(或可以是)空指针常量。您可以在不应该使用 NULL 的地方使用 0;没有地方可以使用不能使用 0 的 NULL。在 C 中,一些系统将 NULL 定义为 ((void *)0) 或附近;这在 C++ 中不起作用(但 NULL 的正确值是编译器的问题,而不是使用 NULL 的程序员)。您可以在我写 0 的地方使用 NULL:char *input = NULL;while (fgets(buffer, sizeof(buffer), stdin) != NULL)if (extra == NULL)
猜你喜欢
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 1970-01-01
  • 2017-03-18
  • 1970-01-01
相关资源
最近更新 更多