将 fgets 与 realloc() 结合使用

Using fgets with realloc()

我正在尝试创建一个函数,使用 fgets() 从文本文件中读取一行,并使用 malloc() 将其存储在动态分配的 char* 中,但我不确定如何使用 realloc(),因为我不知道这一行文本的长度,也不想只是猜测该行可能的最大大小的幻数。

#include "stdio.h"
#include "stdlib.h"
#define INIT_SIZE 50

void get_line (char* filename)

    char* text;
    FILE* file = fopen(filename,"r");

    text = malloc(sizeof(char) * INIT_SIZE);

    fgets(text, INIT_SIZE, file);

    //How do I realloc memory here if the text array is full but fgets
    //has not reach an EOF or \n yet.

    printf(The text was %s\n", text);

    free(text);

int main(int argc, char *argv[]) {
    get_line(argv[1]);
}

我打算用这行文本做其他事情,但为了简单起见,我刚刚打印了它,然后释放了内存。

另外:主要功能是通过使用文件名作为第一个命令行参数来启动的。

一种可能的解决方案是使用两个 缓冲区:调用fgets 时使用的一个临时缓冲区;还有一个是您重新分配的,并将临时缓冲区附加到。

也许是这样的:

char temp[INIT_SIZE];  // Temporary string for fgets call
char *text = NULL;     // The actual and full string
size_t length = 0;     // Current length of the full string, needed for reallocation

while (fgets(temp, sizeof temp, file) != NULL)
{
    // Reallocate
    char *t = realloc(text, length + strlen(temp) + 1);  // +1 for terminator
    if (t == NULL)
    {
        // TODO: Handle error
        break;
    }

    if (text == NULL)
    {
        // First allocation, make sure string is properly terminated for concatenation
        t[0] = '[=10=]';
    }

    text = t;

    // Append the newly read string
    strcat(text, temp);

    // Get current length of the string
    length = strlen(text);

    // If the last character just read is a newline, we have the whole line
    if (length > 0 && text[length - 1] == '\n')
    {
        break;
    }
}

[Discalimer:上面的代码未经测试,可能包含错误]

getline 函数就是您要找的。

这样使用:

char *line = NULL;
size_t n;
getline(&line, &n, stdin);

如果你真的想自己实现这个功能,可以这样写:

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

char *get_line()
{
    int c;
    /* what is the buffer current size? */
    size_t size = 5;
    /* How much is the buffer filled? */
    size_t read_size = 0;
    /* firs allocation, its result should be tested... */
    char *line = malloc(size);
    if (!line) 
    {
        perror("malloc");
        return line;
    }

    line[0] = '[=11=]';

    c = fgetc(stdin);
    while (c != EOF && c!= '\n')
    {            
        line[read_size] = c;            
        ++read_size;
        if (read_size == size)
        {
            size += 5;
            char *test = realloc(line, size);
            if (!test)
            {
                perror("realloc");
                return line;
            }
            line = test;
        }
        c = fgetc(stdin);
    }
    line[read_size] = '[=11=]';
    return line;
}

通过 void get_line (char* filename) 的声明,您永远无法在 get_line 函数之外使用您读取和存储的行,因为您没有 return 指向行和不要传递 的 地址,而不是可以用于在调用函数中进行任何分配和读取可见的任何指针。

任何将未知数量的字符读入单个缓冲区的函数的良好模型(显示 return 类型和有用的参数)总是 POSIX getline。您可以使用 fgetcfgets 和固定缓冲区来实现您自己的。效率仅在将所需的 realloc 调用次数降至最低的情况下才倾向于使用 fgets。 (这两个函数将共享相同的低级输入缓冲区大小,例如,参见 gcc source IO_BUFSIZ 常量——如果我记得最近名称更改后现在是 LIO_BUFSIZE,但基本上归结为 8192 字节 IO 缓冲区 Linux 和 512 字节 windows)

只要您动态分配原始缓冲区(使用malloccallocrealloc),您就可以使用fgets使用固定缓冲区连续读取将读入固定缓冲区的字符添加到分配的行中,并检查最后一个字符是 '\n' 还是 EOF 以确定何时完成。每次迭代只需读取一个固定的字符缓冲区值 fgetsrealloc 你的行,将新字符附加到末尾。

重新分配时,总是 realloc 使用临时指针。这样,如果你 运行 内存不足并且 realloc returns NULL (或由于任何其他原因失败),你不会覆盖指向你当前分配的指针NULL 造成内存泄漏。

一种灵活的实现方式,使用定义的 SZINIT 缓冲区大小(如果用户通过 0)或用户提供的大小来分配初始大小,将固定缓冲区大小设置为 VLA line 的存储(作为指向 char 的指针传递),然后根据需要重新分配,成功时 return 读取的字符数或失败时 -1 的字符数(与 POSIX getline 可以)可以像这样完成:

/** fgetline, a getline replacement with fgets, using fixed buffer.
 *  fgetline reads from 'fp' up to including a newline (or EOF)
 *  allocating for 'line' as required, initially allocating 'n' bytes.
 *  on success, the number of characters in 'line' is returned, -1
 *  otherwise
 */
ssize_t fgetline (char **line, size_t *n, FILE *fp)
{
    if (!line || !n || !fp) return -1;

#ifdef SZINIT
    size_t szinit = SZINIT > 0 ? SZINIT : 120;
#else
    size_t szinit = 120;
#endif

    size_t idx = 0,                 /* index for *line */
        maxc = *n ? *n : szinit,    /* fixed buffer size */
        eol = 0,                    /* end-of-line flag */
        nc = 0;                     /* number of characers read */
    char buf[maxc];     /* VLA to use a fixed buffer (or allocate ) */

    clearerr (fp);                  /* prepare fp for reading */
    while (fgets (buf, maxc, fp)) { /* continuall read maxc chunks */
        nc = strlen (buf);          /* number of characters read */
        if (idx && *buf == '\n')    /* if index & '\n' 1st char */
            break;
        if (nc && (buf[nc - 1] == '\n')) {  /* test '\n' in buf */
            buf[--nc] = 0;          /* trim and set eol flag */
            eol = 1;
        }
        /* always realloc with a temporary pointer */
        void *tmp = realloc (*line, idx + nc + 1);
        if (!tmp)       /* on failure previous data remains in *line */
            return idx ? (ssize_t)idx : -1;
        *line = tmp;    /* assign realloced block to *line */
        memcpy (*line + idx, buf, nc + 1);  /* append buf to line */
        idx += nc;                  /* update index */
        if (eol)                    /* if '\n' (eol flag set) done */
            break;
    }
    /* if eol alone, or stream error, return -1, else length of buf */
    return (feof (fp) && !nc) || ferror (fp) ? -1 : (ssize_t)idx;
}

(注意:因为nc已经保存了buf中的当前字符数,memcpy可以用来追加内容从 buf*line,无需再次扫描终止的 空字符 )仔细阅读,如果您还有其他问题,请告诉我。

本质上,您可以将它用作 POSIX getline 的直接替代品(虽然它不会那么有效——但也不错)