Как прочитать содержимое файла в строке на C?

Каков самый простой способ (наименее подверженный ошибкам, наименьшее количество строк кода, однако вы хотите его интерпретировать) открыть файл на C и прочитать его содержимое в строку (char *, char [], что угодно)?

«Самый простой способ» и «наименее подверженный ошибкам» часто противоположны друг другу.

Andy Lester 06.10.2008 18:37

«Самый простой способ» и «наименьшее количество ошибок» в моей книге на самом деле являются синонимами. Например, ответ на C# - string s = File.ReadAllText(filename);. Как это могло быть проще и больше подвержено ошибкам?

Mark Lakata 07.04.2014 23:54
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
103
2
151 285
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Ответ принят как подходящий

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

Это заглушка, которую я использую для этого. вы также можете проверить коды ошибок для fseek, ftell и fread. (опущено для ясности).

char * buffer = 0;
long length;
FILE * f = fopen (filename, "rb");

if (f)
{
  fseek (f, 0, SEEK_END);
  length = ftell (f);
  fseek (f, 0, SEEK_SET);
  buffer = malloc (length);
  if (buffer)
  {
    fread (buffer, 1, length, f);
  }
  fclose (f);
}

if (buffer)
{
  // start to process your data / extract strings here...
}

Потрясающе, это сработало как шарм (и за ним довольно просто следовать). Спасибо!

Chris Bunch 06.10.2008 18:43

Я бы также проверил возвращаемое значение fread, поскольку он может фактически не читать весь файл из-за ошибок и других причин.

freespace 06.10.2008 18:45

В соответствии с тем, что сказал freespace, вы можете проверить, не слишком ли большой файл. Предположим, например, что кто-то решил загрузить в эту программу файл размером 6 ГБ ...

rmeador 06.10.2008 18:46

Определенно, как изначально сказал Нильс, я собираюсь поискать коды ошибок в fseek, ftell и fread и действовать соответственно.

Chris Bunch 06.10.2008 18:47

Ищете до конца, чтобы вызвать в ftell? Почему бы просто не вызвать статистику?

dicroce 06.10.2008 19:07

как сказал rmeador, fseek не работает с файлами> 4 ГБ.

KPexEA 06.10.2008 19:33

Правда. Для больших файлов это решение отстой.

Nils Pipenbrinck 06.10.2008 19:52

Я не предлагал использовать stat просто потому, что это не ANSI C. (по крайней мере, я так думаю). Afaik "рекомендуемый" способ получить размер файла - это переместиться до конца и получить смещение файла.

Nils Pipenbrinck 06.10.2008 19:53

Это хорошо и легко ... но это задохнется, если вам нужно будет читать из конвейера, а не из обычного файла, что в какой-то момент захочет сделать большинство программ UNIX.

Dan Lenski 06.10.2008 20:27

Поскольку это целевая страница, я хотел бы отметить, что fread не завершает вашу строку нулем. Это может привести к неприятностям.

ivan-k 08.09.2014 22:36

Как сказал @Manbroski, буфер должен быть завершен '\ 0'. Поэтому я бы изменил buffer = malloc (length + 1); и добавил после fclose: buffer[length] = '\0'; (подтверждено Valgrind)

soywod 28.10.2016 11:37

Превратите этот ответ в красивую функцию с проверкой ошибок + пример вызова для копировальных пастеров :-)

Ciro Santilli TRUMP BAN IS BAD 11.03.2017 23:41

fseek (f, 0, SEEK_END); - это явно неопределенное поведение для двоичного потока. 7.21.9.2 Функция fseek, параграф 3: ... Двоичный поток не обязательно должен поддерживать вызовы fseek со значением whenceSEEK_END. и согласно сноске 268 стандарта C: Установка индикатора положения файла на конец файла, как и в случае с fseek(file, 0, SEEK_END), имеет неопределенное поведение для двоичного потока ...

Andrew Henle 09.11.2017 15:17

Я не думаю, что это когда-либо предназначалось для решения с большими файлами. Чтение гигабайт файлов в одну строку - не лучшая идея. Но для файлов меньшего размера это может быть нормально :)

ericcurtin 14.09.2018 14:19

Но как же разделить строки в буфере? Проверяете наличие новых строк?

Zap 31.08.2019 00:06

Если «прочитать его содержимое в строку» означает, что файл не содержит символов с кодом 0, вы также можете использовать функцию getdelim (), которая либо принимает блок памяти и при необходимости перераспределяет его, либо просто выделяет весь буфер для you, и считывает файл в него, пока не встретит указанный разделитель или конец файла. Просто передайте '\ 0' в качестве разделителя, чтобы прочитать весь файл.

Эта функция доступна в библиотеке GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

Пример кода может выглядеть так же просто, как

char* buffer = NULL;
size_t len;
ssize_t bytes_read = getdelim( &buffer, &len, '\0', fp);
if ( bytes_read != -1) {
  /* Success, now the entire file is in the buffer */

Я использовал это раньше! Он работает очень хорошо, если файл, который вы читаете, является текстовым (не содержит \ 0).

ephemient 06.10.2008 20:34

КРАСИВЫЙ! Избавляет от множества проблем при чтении целых текстовых файлов. Если бы существовал аналогичный сверхпростой способ чтения потока двоичных файлов до EOF без использования каких-либо символов-разделителей!

anthony 05.01.2017 06:05

Другое, к сожалению, сильно зависящее от ОС решение - отображение файла в памяти. Преимущества обычно включают в себя производительность чтения и сокращение использования памяти, поскольку просмотр приложений и файловый кеш операционной системы могут фактически совместно использовать физическую память.

Код POSIX будет выглядеть так:

int fd = open("filename", O_RDONLY);
int len = lseek(fd, 0, SEEK_END);
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);

Windows, с другой стороны, немного сложнее, и, к сожалению, у меня нет компилятора для тестирования, но функциональность обеспечивается CreateFileMapping() и MapViewOfFile().

Не забудьте проверить значения, возвращаемые этими системными вызовами!

Toby Speight 28.02.2018 13:50

при вызове lseek () необходимо использовать off_t вместо int.

ivan.ukr 12.07.2018 08:06

Обратите внимание, что если цель состоит в том, чтобы стабильно фиксировать в памяти содержимое файла в данный момент времени, этого решения следует избегать, если вы не уверены, что файл, считываемый в память, не будет изменен другими процессами в течение интервала. над которым будет использоваться карта. См. Этот Почта для получения дополнительной информации.

user001 20.05.2019 08:56

Если файл текстовый, и вы хотите получить текст построчно, проще всего использовать fgets ().

char buffer[100];
FILE *fp = fopen("filename", "r");                 // do not use "rb"
while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);

Если вы читаете специальные файлы, такие как stdin или pipe, вы не сможете использовать fstat для получения размера файла заранее. Кроме того, если вы читаете двоичный файл, fgets потеряет информацию о размере строки из-за встроенных символов '\ 0'. Лучший способ прочитать файл - использовать чтение и перераспределение:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main () {
    char buf[4096];
    ssize_t n;
    char *str = NULL;
    size_t len = 0;
    while (n = read(STDIN_FILENO, buf, sizeof buf)) {
        if (n < 0) {
            if (errno == EAGAIN)
                continue;
            perror("read");
            break;
        }
        str = realloc(str, len + n + 1);
        memcpy(str + len, buf, n);
        len += n;
        str[len] = '\0';
    }
    printf("%.*s\n", len, str);
    return 0;
}

Это O (n ^ 2), где n - длина вашего файла. Все решения с большим количеством голосов - O (n). Пожалуйста, не используйте это решение на практике или используйте модифицированную версию с мультипликативным ростом.

Clark Gaebel 24.02.2016 22:26

realloc () может расширить существующую память до нового размера, не копируя старую память в новую большую часть памяти. только если есть промежуточные вызовы malloc (), потребуется переместить память и сделать это решение O (n ^ 2). здесь нет вызовов malloc (), которые происходят между вызовами realloc (), поэтому решение должно быть в порядке.

Jake 07.03.2016 21:42

Вы можете читать прямо в буфер «str» (с соответствующим смещением), без необходимости копировать из промежуточного «buf». Однако этот метод обычно приводит к чрезмерному выделению памяти, необходимой для содержимого файла. Также следите за двоичными файлами, printf не будет обрабатывать их правильно, и вы, вероятно, все равно не захотите печатать двоичные файлы!

anthony 05.01.2017 06:14

// Assumes the file exists and will seg. fault otherwise.
const GLchar *load_shader_source(char *filename) {
  FILE *file = fopen(filename, "r");             // open 
  fseek(file, 0L, SEEK_END);                     // find the end
  size_t size = ftell(file);                     // get the size in bytes
  GLchar *shaderSource = calloc(1, size);        // allocate enough bytes
  rewind(file);                                  // go back to file beginning
  fread(shaderSource, size, sizeof(char), file); // read each char into ourblock
  fclose(file);                                  // close the stream
  return shaderSource;
}

Это довольно грубое решение, потому что ничего не проверяется на нулевое значение.

Это будет только с дисковыми файлами. Он не будет работать для именованных каналов, стандартного ввода или сетевых потоков.

anthony 05.01.2017 06:14

Ха, и зачем я сюда приехал! Но я думаю, что вам нужно либо завершить строку нулем, либо вернуть длину, которую необязательно принимает glShaderSource.

Ciro Santilli TRUMP BAN IS BAD 11.03.2017 23:52

Если вы используете glib, вы можете использовать g_file_get_contents;

gchar *contents;
GError *err = NULL;

g_file_get_contents ("foo.txt", &contents, NULL, &err);
g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL));
if (err != NULL)
  {
    // Report error to user, and free error
    g_assert (contents == NULL);
    fprintf (stderr, "Unable to read file: %s\n", err->message);
    g_error_free (err);
  }
else
  {
    // Use file contents
    g_assert (contents != NULL);
  }
}

Только что изменено из принятого ответа выше.

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

char *readFile(char *filename) {
    FILE *f = fopen(filename, "rt");
    assert(f);
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buffer = (char *) malloc(length + 1);
    buffer[length] = '\0';
    fread(buffer, 1, length, f);
    fclose(f);
    return buffer;
}

int main() {
    char *content = readFile("../hello.txt");
    printf("%s", content);
}

Это не код C. Вопрос не помечен как C++.

Gerhardh 09.11.2017 10:18

@Gerhardh Такой быстрый ответ на вопрос девять лет назад, когда я редактирую! Хотя функциональная часть - это чистый C, я прошу прощения за свой ответ will-not-run-on-c.

BaiJiFeiLong 09.11.2017 10:25

Этот древний вопрос был включен в список активных вопросов. Я не искал.

Gerhardh 09.11.2017 10:28

Этот код приводит к утечке памяти, не забудьте освободить память malloc'd :)

ericcurtin 14.09.2018 15:05

Примечание. Это модификация принятого выше ответа.

Вот способ сделать это с проверкой ошибок.

Я добавил средство проверки размера, чтобы закрыть его, когда файл был больше 1 ГиБ. Я сделал это, потому что программа помещает весь файл в строку, которая может использовать слишком много оперативной памяти и привести к сбою компьютера. Однако, если вас это не волнует, вы можете просто удалить это из кода.

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

#define FILE_OK 0
#define FILE_NOT_EXIST 1
#define FILE_TO_LARGE 2
#define FILE_READ_ERROR 3

char * c_read_file(const char * f_name, int * err, size_t * f_size) {
    char * buffer;
    size_t length;
    FILE * f = fopen(f_name, "rb");
    size_t read_length;
    
    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek(f, 0, SEEK_SET);
        
        // 1 GiB; best not to load a whole large file in one string
        if (length > 1073741824) {
            *err = FILE_TO_LARGE;
            
            return NULL;
        }
        
        buffer = (char *)malloc(length + 1);
        
        if (length) {
            read_length = fread(buffer, 1, length, f);
            
            if (length != read_length) {
                 free(buffer);
                 *err = FILE_READ_ERROR;

                 return NULL;
            }
        }
        
        fclose(f);
        
        *err = FILE_OK;
        buffer[length] = '\0';
        *f_size = length;
    }
    else {
        *err = FILE_NOT_EXIST;
        
        return NULL;
    }
    
    return buffer;
}

И чтобы проверить ошибки:

int err;
size_t f_size;
char * f_data;

f_data = c_read_file("test.txt", &err, &f_size);

if (err) {
    // process error
}
else {
    // process data
    free(f_data);
}

Только один вопрос: buffer, который вы выделили с malloc(length +1), не освобождается. Это то, что должен делать потребитель этого метода, или нет необходимости в выделенной памяти free()?

Pablosproject 17.09.2020 12:44

если ошибки не произошло - бесплатно (f_data); должен называться. спасибо за указание на это

Joe Cool 20.09.2020 03:23

Я добавлю свою версию, основанную на ответах здесь, просто для справки. Мой код учитывает sizeof (char) и добавляет к нему несколько комментариев.

// Open the file in read mode.
FILE *file = fopen(file_name, "r");
// Check if there was an error.
if (file == NULL) {
    fprintf(stderr, "Error: Can't open file '%s'.", file_name);
    exit(EXIT_FAILURE);
}
// Get the file length
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
// Create the string for the file contents.
char *buffer = malloc(sizeof(char) * (length + 1));
buffer[length] = '\0';
// Set the contents of the string.
fread(buffer, sizeof(char), length, file);
// Close the file.
fclose(file);
// Do something with the data.
// ...
// Free the allocated string space.
free(buffer);

легко и аккуратно (при условии, что содержимое файла меньше 10000):

void read_whole_file(char fileName[1000], char buffer[10000])
{
    FILE * file = fopen(fileName, "r");
    if (file == NULL)
    {
        puts("File not found");
        exit(1);
    }
    char  c;
    int idx=0;
    while (fscanf(file , "%c" ,&c) == 1)
    {
        buffer[idx] = c;
        idx++;
    }
    buffer[idx] = 0;
}

Пожалуйста, не выделяйте заранее всю необходимую считать память. Это прекрасный пример плохого дизайна. Вы должны выделять память на ходу, когда это возможно. Было бы неплохо, если бы вы ожидали, что файл будет иметь длину 10 000 байт, ваша программа не сможет обработать файл любого другого размера, и вы все равно проверяете размер и выявляете ошибки, но это не то, что здесь происходит. Вам действительно стоит научиться правильно кодировать C.

Jack Giffin 24.07.2020 03:21

Другие вопросы по теме