Использование fgets с realloc () - PullRequest
       60

Использование fgets с realloc ()

0 голосов
/ 25 октября 2018

Я пытаюсь создать функцию для чтения одной строки из файла текста, используя fgets(), и сохранить его в динамически распределяемом символе *, используя malloc(), но я не уверен, как использовать 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]);
}

Я планирую заняться другими деламисо строкой текста, но для простоты я просто распечатал ее, а затем освободил память.

Также: функция main запускается с использованием имени файла в качестве первого аргумента командной строки.

Ответы [ 3 ]

0 голосов
/ 25 октября 2018

Функция 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] = '\0';

    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] = '\0';
    return line;
}
0 голосов
/ 25 октября 2018

С объявлением void get_line (char* filename) вы никогда не сможете использовать строку, которую вы читаете и храните вне функции get_line, потому что вы не возвращаете указатель на строку и не передаете адрес для любой указатель, который мог бы служить для выделения и чтения видимым обратно в вызывающей функции.

Хорошая модель (показывающая тип возвращаемого значения и полезные параметры) для любой функции для считывания неизвестного количества символов в одинбуфер всегда POSIX getline.Вы можете реализовать свои собственные, используя fgetc из fgets и фиксированный буфер.Эффективность способствует использованию fgets только в той степени, в которой это минимизирует количество необходимых вызовов realloc.(обе функции будут использовать один и тот же низкоуровневый размер входного буфера, например, см. gcc source IO_BUFSIZ константа - которая, если я помню, теперь LIO_BUFSIZE после недавнего изменения имени, но в основном сводится к 8192 байтовому вводу-выводубуфер в Linux и 512 байт в Windows)

Пока вы динамически выделяете исходный буфер (используя malloc, calloc или realloc), вы можете непрерывно читать с фиксированным буферомиспользуя fgets добавление символов, считанных в фиксированный буфер, к выделенной строке и проверяя, является ли последний символ '\n' или EOF, чтобы определить, когда вы закончите.Просто прочитайте фиксированный буфер из символов с fgets каждой итерацией и realloc вашей строкой по ходу, добавляя новые символы в конец.

При перераспределении всегда realloc с использованием временного указателя .Таким образом, если вам не хватит памяти и realloc вернет NULL (или произойдет сбой по любой другой причине), вы не будете перезаписывать указатель на выделенный в данный момент блок, если NULL создаст утечку памяти.

Гибкая реализация, которая изменяет размер фиксированного буфера как VLA, используя либо SZINIT для размера буфера (если пользователь передает 0), либо размер, предоставленный пользователем для выделения начального хранилища для line (передается как указатель на указатель на символ), а затем перераспределяется по мере необходимости, возвращая количество символов, прочитанных при успехе, или -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 без сканирования дляокончание nul-символ еще раз) Просмотрите его и дайте мне знать, если у вас есть дополнительные вопросы.

По сути вы можете использовать его в качестве замены для POSIX getline (хотя это будет не так эффективно, но и неплохо)

0 голосов
/ 25 октября 2018

Одним из возможных решений является использование двух буферов: один временный, который вы используете при вызове 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] = '\0';
    }

    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: приведенный выше код не проверен и может содержать ошибки]

...