从文件中动态读取C语言中的一行

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

int main()
{
    FILE *input_f;

    input_f = fopen("Input.txt", "r"); //Opens the file in read mode.
    if (input_f != NULL)
    {
        char line[2048];

        while( fgets(line, sizeof line, input_f) != NULL )
        {
            //do something
        }
        fclose(input_f); //Close the input file.
    }
    else
    {
        perror("File couldn't opened"); //Will print that file couldn't opened and why.
    }
    return 0;
}

你好。我知道在C语言中可以使用以下代码逐行读取,但我不想限制行大小,比如像这个代码一样限制为2048。

char line[2048];
while (fgets(line, sizeof(line), file)) {
    // do something with line
}

我考虑使用malloc,但是在读取行之前我不知道行的大小,所以我认为这是不可能做到的。

有没有办法不限制行大小呢?

这个问题只是出于我的好奇心,请谢谢。


5
你可以随时使用 getchar() + realloc(),还有 getline() - undefined
2
https://dev59.com/aHE85IYBdhLWcg3w8IbK - undefined
我对一个类似问题的回答:http://stackoverflow.com/questions/28254245/c-reading-a-text-file-separated-by-spaces-with-unbounded-word-size/28255082#28255082 - undefined
2
想法:虽然有不限制大小的需求,但即使是动态读取行也受益于设定一个上限来防止恶意滥用。例如,在读取用户输入的一行超过1MB时,最好不要接受更长的行,因为它很可能是黑客攻击而不是合法的。 - undefined
2个回答

1

当您动态分配内存时,您将希望更改:

char line[2048];

#define MAXL 2048           /* the use of a define will become apparent when you  */
size_t maxl = MAXL;         /* need to check to determine if a realloc is needed  */
char *line = malloc (maxl * sizeof *line);
if (!line)                  /* always check to insure allocation succeeded */
    ...error.. memory allocation failed

您可以读取最多 (maxl -1) 个字符或者一个 newline(如果使用 fgetc 等函数),或者读取整行并检查 line [strlen (line) - 1] == '\n' 来确定是否已经读取了整行(如果使用 fgets 函数)。 (POSIX 要求每行都以 newline 结尾) 如果您读取了 maxl 个字符(使用 fgetc 函数)或者没有读取到 newline(使用 fgets 函数),那么这是一个短读取,还有更多的字符未被读取。您可以选择使用 realloc(通常将大小加倍)并再次尝试。用 realloc:

char *tmp = realloc (line, 2 * maxl)
if (tmp) {
    line = tmp;
    maxl *= 2;
}

注意:永远不要使用原始指针重新分配内存(例如 line = realloc(line, 2 * maxl)),因为如果 realloc 失败,内存将被释放并将指针设置为 NULL,你将会丢失在 line 中存在的任何数据。此外,请注意,maxl 通常每次 realloc 都会加倍。但是,你可以自由选择任何大小增加方案。(如果你担心需要将新分配的内存全部清零,可以使用 memset 将新分配的空间初始化为零/空。在某些情况下很有用,例如你希望确保你的 line 总是以 null 结尾)
这是基本的动态分配/重新分配方案。请注意,你需要读取完整行才能结束读取,因此你需要重新构造循环测试。最后,由于你分配了内存,所以当你完成使用时,你负责释放内存。你无法离开的工具是 valgrind(或类似的内存检查器),以确认你没有泄漏内存。

提示如果您正在阅读并希望确保您的字符串始终是null-terminated,那么在分配内存块之后,请将所有字符都设置为零(0)。如前所述,memset可用,但如果您选择calloc而不是malloc,它将为您清零内存。但是,在realloc上,新空间也不会被清零,因此无论最初分配块的函数是什么,都需要调用memset

提示2查看POSIX getline。只要line初始化为NULLgetline就会处理所需的分配/重新分配。 getline还返回实际读取的字符数,省去了在fgets之后调用strlen来确定相同字符数的需要。

如果您有其他问题,请告诉我。


不同意使用“初始化所有新分配的内存为零”是“如果想确保你的行始终以空字符结尾”是有用的。检查fgets()的返回值是最好的做法。如果返回值不是NULL(且size > 0),那么缓冲区将始终以空字符结尾。 - undefined
我考虑过这个问题。这并不适用于所有情况,但如何向概念中引入新的分配器呢?我会再考虑一下并重新表述。你有什么建议吗? - undefined
@chux 这个资格怎么样?可以尽情发挥创造力。重点是要帮助楼主学习。 - undefined
检查返回值是关键。如果代码不检查返回值,在发生IO错误的情况下,缓冲区的状态是未知的。因此,预先填充没有保证的行为。预先填充缓冲区的一个有用原因是在调试时,程序员可能更容易观察到其行为。 - undefined

0
考虑两个想法:
1. 分配的内存上限是合理的。任务的性质应该有一个最大行长度的概念,无论是80、1024还是1兆字节。
2. 通过聪明的操作系统,分配的内存实际使用可能只在需要时发生。参见为什么malloc不会“占用”我的计算机内存? 因此,让代码分配一个大缓冲区来限制病态情况,并让底层内存管理根据需要重新分配真实内存。
#define N (1000000)
char *buf = malloc(N);
...
while (fgets(buf, N, stdin) != NULL)) {
  size_t len = strlen(buf);
  if (len == N-1) {
    perror("Excessive Long Line");
    exit(EXIT_FAILURE);
  }
}
free(buf);

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