如何使用fgets读取未知长度的输入

9

我该如何使用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;
}

2
看看realloc。考虑每次将分配加倍的策略,以及直接读入缓冲区。顺便说一下:size_t的printf格式是%zu - Deduplicator
1
如果您有,考虑使用getline而不是fgets - 5gon12eder
我知道getline,但我必须使用fgets。 - lllook
2
你的代码在 malloc() 中存在一个经典的 off-by-one 错误。使用 strlen(str) 函数几乎总是错误的;它几乎总是应该是 strlen(str)+1。此外,你的循环也会严重泄漏内存;在每次迭代中,你都会覆盖存储在 input 中的前一个指针。 - Jonathan Leffler
小心有人无意或恶意地给你一个二进制文件。在这种情况下,你可能不想打印它。 - anthony
显示剩余5条评论
2个回答

5
#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将其大小调整为前一次的两倍? - lllook
不,最简单的方法是使用char buffer[4096];并继续生活。只有JSON超过这个长度,然后您可以在一行中读取整个文件。(我夸张了,但并不完全是这样。)如果失败,想法是您首次分配10个字节,然后重新分配20个字节,然后重新分配40个字节(但将2个10个单位读入额外空间),然后80、160、320...如果您不打算在函数退出之前释放空间,并且有超过64个字节未使用,您可以使用realloc()将分配缩小到所需大小。 - Jonathan Leffler
除非你在使用微型机器,否则4 KiB缓冲区不会给你带来麻烦。如果你在使用这样的微型机器,你可能根本不会使用malloc() - 或者读取如此长的行。 - Jonathan Leffler
感谢您的回答,在您的修改中,if(extra == 0) break; 这段代码是什么意思?这里的0表示'NULL'吗? - lllook
NULL在某种程度上是0的另一个名称。虽然存在一些复杂性,但两者都是(或可以是)空指针常量。您可以在不应使用NULL的位置使用0;您可以在任何可以使用0的地方使用NULL。在C中,一些系统将NULL定义为((void *)0)或类似内容;这在C++中行不通(但是NULL的正确值是编译器的问题,而不是使用NULL的程序员的问题)。您可以在以下代码中使用NULL替换0:char *input = NULL;while (fgets(buffer, sizeof(buffer), stdin) != NULL)if (extra == NULL) - Jonathan Leffler
请问您能否演示一下如何实现此操作并且每次调整大小2次呢? - lllook

3
更好的方法是使用可以为您分配内存的输入机制,例如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 example

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

1
如果你在Windows上工作,使用codeblocks时,scanf不会分配内存,可以使用getline。我将为Linux更新。我使用带有gnu扩展的gcc/C89 - David C. Rankin
摆脱代码块 :) 我已经有其他人报告说在使用 Codeblocks 时无法分配 scanf,所以这一定是一个限制。 - David C. Rankin
Code::Blocks与编译无关,它只是一个集成开发环境。 - ryyker
底线是什么?我收到了另一个报告,使用在Windows上使用Codeblocks的gcc设置时,scanf无法分配内存。我既没有这个设置,也没有测试的方法。 - David C. Rankin
能否分配内存取决于OP使用的编译器实现。我曾经在几个项目中使用MinGW实现,没有遇到任何问题,但现在没有它来测试这个问题。 - ryyker
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接