Попытка прочитать неизвестную длину строки из файла с помощью fgetc()

Так что да, видел много похожих вопросов на этот, но решил попробовать решить его по-своему. Получение огромного количества текстовых блоков после запуска (компилируется нормально).

Я пытаюсь получить строку неизвестного размера из файла. Думал о выделении точек размером 2 (1 символ и нулевой терминатор), а затем использовать malloc для увеличения размера массива символов для каждого символа, превышающего размер массива.

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

int main()
{
    char *pts = NULL;
    int temp = 0;

    pts = malloc(2 * sizeof(char));
    FILE *fp = fopen("txtfile", "r");
    while (fgetc(fp) != EOF) {
        if (strlen(pts) == temp) {
            pts = realloc(pts, sizeof(char));
        }
        pts[temp] = fgetc(fp);
        temp++;
    }

    printf("the full string is a s follows : %s\n", pts);
    free(pts);
    fclose(fp);

    return 0;
}

person Hacktivator    schedule 17.12.2019    source источник
comment
Интересный вопрос, а в чем именно проблема? Если вы пытаетесь получить строку неизвестного размера, а в результате получается огромный блок текста, разве это не успех? Похоже, вы прочитали мне весь файл.   -  person Nick Reed    schedule 17.12.2019
comment
В этот момент - strlen(pts) вы не знаете, что находится внутри pts, и вы вызываете strlen() на нем, что приводит к UB. Может быть, calloc() будет лучшим выбором?   -  person babon    schedule 17.12.2019
comment
В худшем случае строка имеет тот же размер, что и файл; Вы можете просто выделить столько памяти?   -  person Fiddling Bits    schedule 17.12.2019
comment
pts = realloc(pts, sizeof(char)) не расширяет буфер, а всегда выделяет 1 байт. Вы должны указать общую длину   -  person Ctx    schedule 17.12.2019
comment
Что происходит каждый раз, когда выполняется это: while (fgetc(fp) != EOF) {? Из файла что-то читается, но куда?   -  person Adrian Mole    schedule 17.12.2019
comment
Более того, вы, скорее всего, не захотите вызывать fgetc() дважды в одной итерации, когда вы присваиваете pts[temp] только один раз.   -  person babon    schedule 17.12.2019
comment
malloc, realloc и fopen могут выйти из строя. Вы должны прочитать документацию по этим функциям (особенно realloc) и соответствующим образом обрабатывать их ошибки.   -  person Sean Bright    schedule 17.12.2019


Ответы (3)


Вы, вероятно, хотите что-то вроде этого:

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

#define CHUNK_SIZE 1000               // initial buffer size

int main()
{
  int ch;                             // you need int, not char for EOF
  int size = CHUNK_SIZE;

  char *pts = malloc(CHUNK_SIZE);
  FILE* fp = fopen("txtfile", "r");

  int i = 0;
  while ((ch = fgetc(fp)) != EOF)     // read one char until EOF 
  {
    pts[i++] = ch;                    // add char into buffer

    if (i == size + CHUNK_SIZE)       // if buffer full ...
    {
      size += CHUNK_SIZE;             // increase buffer size
      pts = realloc(pts, size);       // reallocate new size
    }
  }

  pts[i] = 0;                        // add NUL terminator

  printf("the full string is a s follows : %s\n", pts);
  free(pts);
  fclose(fp);

  return 0;
}

Отказ от ответственности:

  1. это непроверенный код, он может не работать, но идею показывает
  2. абсолютно нет проверки ошибок для краткости, вы должны добавить это.
  3. есть место для других улучшений, наверное можно сделать еще изящнее
person Jabberwocky    schedule 17.12.2019
comment
Почему бы просто не посчитать длину файла, выделить память под этот объем и потом записать? realloc, кажется, добавляет ненужную сложность. - person Fiddling Bits; 17.12.2019
comment
@FiddlingBits как узнать размер стандартного ввода? - person Jabberwocky; 17.12.2019
comment
Это хороший момент, но OP конкретно спрашивает о файле. - person Fiddling Bits; 17.12.2019
comment
Общее решение более гибкое, но вы правы в том, что вы также можете проверить размер файла (например, с stat()) и выделить один раз. - person Sean Bright; 17.12.2019
comment
@FiddlingBits смысл также в том, чтобы показать, как правильно выполнять операции realloc, но да, если речь идет только о чтении файла полностью в память, а затем о получении длины файла, выделении правильного размера памяти и чтении файла с помощью одного fread определенно проще и эффективнее. - person Jabberwocky; 17.12.2019

Оставим пока в стороне вопрос о том, нужно ли вам вообще это делать:

Вы довольно близки к этому решению, но есть несколько ошибок

while (fgetc(fp) != EOF) {

Эта строка будет читать один символ из файла, а затем отбрасывать его после сравнения с EOF. Вам нужно будет сохранить этот байт, чтобы добавить его в буфер. Такой тип синтаксиса, как while ((tmp=fgetc(fp)) != EOF), должен работать.

pts = realloc(pts, sizeof(char));

Проверьте документацию для realloc, вам нужно указать новый размер во втором параметре.

pts = malloc(2 * sizeof(char));

Вам нужно будет обнулить эту память после ее получения. Вероятно, вы также захотите обнулить любую память, предоставленную вам realloc, иначе вы можете потерять ноль в конце строки, и strlen будет неверным.


Но, как я упоминал ранее, использование realloc в подобном цикле, когда у вас уже есть четкое представление о размере буфера, как правило, не является идиоматичным дизайном C. Получите размер файла заранее и выделите достаточно места для всех данных в вашем буфере. Вы все еще можете перераспределить память, если превысите размер буфера, но сделайте это, используя куски памяти, а не по одному байту за раз.

person Segfault    schedule 17.12.2019
comment
Разве вы не должны проверять '\0' вместо EOF? - person Fiddling Bits; 17.12.2019
comment
Нуль - это маркер конца строки для буферов в памяти в C. Сам файл не обязательно должен заканчиваться нулем или вообще иметь в нем какие-либо нули. Дополнительная информация: en.cppreference.com/w/c/io/fgetc - person Segfault; 17.12.2019
comment
Вам нужно будет обнулить эту память после ее получения. - Это верно для сломанной реализации OP, но если вы правильно добавите терминатор 0 в конце, это будет ненужным. - person Sean Bright; 17.12.2019
comment
@Segfault Хейя, прежде всего спасибо за ваш вклад: D. Я вообще не понял третью вещь (обнуление)... не могли бы вы подробнее рассказать об этой теме? И да, я знаю, что было бы разумнее получить размер самого файла, но я хотел продвинуться в технике, чтобы ее можно было передавать в другие источники информации. Это может быть файл или консольная команда, или мы, поэтому я попытался получить универсальный код для работы с нашим источником ввода, который я получил. - person Hacktivator; 17.12.2019
comment
Суть обнуления заключается в том, чтобы убедиться, что ваши буферы (строки) правильно завершаются нулем. Шон Брайт прав, когда говорит, что если вы правильно добавите нулевой терминатор, это не проблема. Проблема в том, что malloc и realloc возвращают вам неинициализированную память, поэтому в ней будут какие-то мусорные данные. Malloc не инициализирует память до нуля, прежде чем передать ее вам. Когда вы начинаете записывать данные в буфер, который вы выделили через malloc, он не будет завершаться нулем, если только вы не сделаете это сами. Обнуление всех байтов после их выделения — это простой способ сделать это. - person Segfault; 18.12.2019

Вероятно, наиболее эффективным способом является (как указано в comment by Fiddling Bits) заключается в чтении всего файла за один раз (после получения первого размера файла):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>

int main()
{
    size_t nchars = 0; // Declare here and set to zero...
    // ... so we can optionally try using the "stat" function, if the O/S supports it...
    struct stat st;
    if (stat("txtfile", &st) == 0) nchars = st.st_size;

    FILE* fp = fopen("txtfile", "rb"); // Make sure we open in BINARY mode!
    if (nchars == 0) // This code will be used if the "stat" function is unavailable or failed ...
    {
        fseek(fp, 0, SEEK_END); // Go to end of file (NOTE: SEEK_END may not be implemented - but PROBABLY is!)
    //  while (fgetc(fp) != EOF) {} // If your system doesn't implement SEEK_END, you can do this instead:
        nchars = (size_t)(ftell(fp)); // Add one for NUL terminator
    }
    char* pts = calloc(nchars + 1, sizeof(char));

    if (pts != NULL)
    {
        fseek(fp, 0, SEEK_SET); // Return to start of file...
        fread(pts, sizeof(char), nchars, fp); // ... and read one great big chunk!
        printf("the full string is a s follows : %s\n", pts);
        free(pts);
    }
    else
    {
        printf("the file is too big for me to handle (%zu bytes)!", nchars);
    }
    fclose(fp);
    return 0;
}

По вопросу использования SEEK_END см. эту страницу cppreference, где указано :

  • Реализациям библиотек разрешено не поддерживать SEEK_END (поэтому код, использующий его, не имеет реальной стандартной переносимости).

О том, сможете ли вы использовать функцию stat, см. в этой Википедии. страница. (Но теперь он доступен в версии MSVC для Windows!)

person Adrian Mole    schedule 17.12.2019
comment
Просто используйте stat(). - person Sean Bright; 17.12.2019
comment
@Sean - Но это доступно только в POSIX, IIRC. (Или это только Linux/Unix?) - person Adrian Mole; 17.12.2019
comment
Это POSIX. Я исхожу из предположения, что ОС OP поддерживает это. - person Sean Bright; 17.12.2019
comment
@SeanBright Я добавил параметр stat() (и проверку ошибок). Спасибо за подсказку! - person Adrian Mole; 17.12.2019