从 stdin 读取未知长度的未知行数

Reading an unknown number of lines with unknown length from stdin

我是 C 语言编程的新手,我正在尝试使用 fgets[=24 从 stdin 读取输入=].

首先,我想阅读最多 50 行,每行最多 50 个字符,并且有如下内容:

int max_length = 50;
char lines[max_length][max_length];
char current_line[max_length];
int idx = 0;

while(fgets(current_line, max_length, stdin) != NULL) {
    strcopy(lines[idx], current_line);
    idx++;
}

上面的代码片段成功读取输入并将其存储到 lines 数组中,我可以在其中排序和打印它。

我的问题是如何处理未知行数,每行字符数未知? (请记住,我将不得不对这些行进行排序并打印出来)。

检查发现 here 的 GetString 函数。

虽然已经回答了这个问题的许多不同变体,但是关于如何解决这个问题的考虑可以用一个段落。遇到这个问题时,无论您使用哪种库或 POSIX 函数组合,方法都是相同的。

本质上,您将动态分配合理数量的字符来容纳每一行。 POSIX getline 会自动为你做这件事,使用 fgets 你可以简单地读取一个充满字符的固定缓冲区并附加它们(根据需要重新分配存储)直到 '\n' 字符已读取(或达到 EOF

如果你使用getline,那么你必须分配内存,并复制缓冲区填充。否则,您将用读取的每一行新行覆盖以前的行,并且当您尝试 free 每行时,您可能会在重复尝试时出现 double-free 或 corruption 的 SegFault释放相同的内存块。

您可以使用strdup 简单地复制缓冲区。但是,由于 strdup 分配存储空间,您应该先验证分配是否成功,然后再将指向新内存块的指针分配给您的行集合。

要访问每一行,您需要一个指向每一行开头的指针(保存每一行的内存块)。通常使用指向 char 的指针的 指针。 (例如 char **lines;)内存分配通常通过分配一些合理数量的指针来处理,跟踪您使用的数量,当您达到您分配的数量时,您 realloc 并加倍指针的数量。

与每次读取一样,您需要验证每个内存分配。 (每个 malloccallocrealloc)您还需要通过内存错误检查程序 运行 验证您的程序使用您分配的内存的方式(例如 valgrind 对应 Linux)。它们使用简单,只需 valgrind yourexename.

将这些部分放在一起,您可以执行类似于以下操作的操作。下面的代码将从作为程序的第一个参数提供的文件名中读取所有行(如果没有提供参数,则默认从 stdin 读取)并打印行号和行到 stdout (保留那个请记住,如果你 运行 它在一个 50,000 行的文件中)

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

#define NPTR 8

int main (int argc, char **argv) {

    size_t ndx = 0,             /* line index */
        nptrs = NPTR,           /* initial number of pointers */
        n = 0;                  /* line alloc size (0, getline decides) */
    ssize_t nchr = 0;           /* return (no. of chars read by getline) */
    char *line = NULL,          /* buffer to read each line */
        **lines = NULL;         /* pointer to pointer to each line */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    /* allocate/validate initial 'nptrs' pointers */
    if (!(lines = calloc (nptrs, sizeof *lines))) {
        fprintf (stderr, "error: memory exhausted - lines.\n");
        return 1;
    }

    /* read each line with POSIX getline */
    while ((nchr = getline (&line, &n, fp)) != -1) {
        if (nchr && line[nchr - 1] == '\n') /* check trailing '\n' */
            line[--nchr] = 0;               /* overwrite with nul-char */
        char *buf = strdup (line);          /* allocate/copy line */
        if (!buf) {     /* strdup allocates, so validate */
            fprintf (stderr, "error: strdup allocation failed.\n");
            break;
        }
        lines[ndx++] = buf;     /* assign start address for buf to lines */
        if (ndx == nptrs) {     /* if pointer limit reached, realloc */
            /* always realloc to temporary pointer, to validate success */
            void *tmp = realloc (lines, sizeof *lines * nptrs * 2);
            if (!tmp) {         /* if realloc fails, bail with lines intact */
                fprintf (stderr, "read_input: memory exhausted - realloc.\n");
                break;
            }
            lines = tmp;        /* assign reallocted block to lines */
            /* zero all new memory (optional) */
            memset (lines + nptrs, 0, nptrs * sizeof *lines);
            nptrs *= 2;         /* increment number of allocated pointers */
        }
    }
    free (line);                    /* free memory allocated by getline */

    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (size_t i = 0; i < ndx; i++) {
        printf ("line[%3zu] : %s\n", i, lines[i]);
        free (lines[i]);            /* free memory for each line */
    }
    free (lines);                   /* free pointers */

    return 0;
}

如果您没有 getlinestrdup,您可以轻松实现每一个。每个网站上都有多个示例。如果找不到,请告诉我。如果您还有其他问题,请告诉我。