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





Я стараюсь просто загружать весь буфер как необработанный фрагмент памяти в память и выполнять синтаксический анализ самостоятельно. Таким образом, я лучше всего контролирую, что стандартная библиотека делает на нескольких платформах.
Это заглушка, которую я использую для этого. вы также можете проверить коды ошибок для 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...
}
Потрясающе, это сработало как шарм (и за ним довольно просто следовать). Спасибо!
Я бы также проверил возвращаемое значение fread, поскольку он может фактически не читать весь файл из-за ошибок и других причин.
В соответствии с тем, что сказал freespace, вы можете проверить, не слишком ли большой файл. Предположим, например, что кто-то решил загрузить в эту программу файл размером 6 ГБ ...
Определенно, как изначально сказал Нильс, я собираюсь поискать коды ошибок в fseek, ftell и fread и действовать соответственно.
Ищете до конца, чтобы вызвать в ftell? Почему бы просто не вызвать статистику?
как сказал rmeador, fseek не работает с файлами> 4 ГБ.
Правда. Для больших файлов это решение отстой.
Я не предлагал использовать stat просто потому, что это не ANSI C. (по крайней мере, я так думаю). Afaik "рекомендуемый" способ получить размер файла - это переместиться до конца и получить смещение файла.
Это хорошо и легко ... но это задохнется, если вам нужно будет читать из конвейера, а не из обычного файла, что в какой-то момент захочет сделать большинство программ UNIX.
Поскольку это целевая страница, я хотел бы отметить, что fread не завершает вашу строку нулем. Это может привести к неприятностям.
Как сказал @Manbroski, буфер должен быть завершен '\ 0'. Поэтому я бы изменил buffer = malloc (length + 1); и добавил после fclose: buffer[length] = '\0'; (подтверждено Valgrind)
Превратите этот ответ в красивую функцию с проверкой ошибок + пример вызова для копировальных пастеров :-)
fseek (f, 0, SEEK_END); - это явно неопределенное поведение для двоичного потока. 7.21.9.2 Функция fseek, параграф 3: ... Двоичный поток не обязательно должен поддерживать вызовы fseek со значением whenceSEEK_END. и согласно сноске 268 стандарта C: Установка индикатора положения файла на конец файла, как и в случае с fseek(file, 0, SEEK_END), имеет неопределенное поведение для двоичного потока ...
Я не думаю, что это когда-либо предназначалось для решения с большими файлами. Чтение гигабайт файлов в одну строку - не лучшая идея. Но для файлов меньшего размера это может быть нормально :)
Но как же разделить строки в буфере? Проверяете наличие новых строк?
Если «прочитать его содержимое в строку» означает, что файл не содержит символов с кодом 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).
КРАСИВЫЙ! Избавляет от множества проблем при чтении целых текстовых файлов. Если бы существовал аналогичный сверхпростой способ чтения потока двоичных файлов до EOF без использования каких-либо символов-разделителей!
Другое, к сожалению, сильно зависящее от ОС решение - отображение файла в памяти. Преимущества обычно включают в себя производительность чтения и сокращение использования памяти, поскольку просмотр приложений и файловый кеш операционной системы могут фактически совместно использовать физическую память.
Код 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().
Не забудьте проверить значения, возвращаемые этими системными вызовами!
при вызове lseek () необходимо использовать off_t вместо int.
Обратите внимание, что если цель состоит в том, чтобы стабильно фиксировать в памяти содержимое файла в данный момент времени, этого решения следует избегать, если вы не уверены, что файл, считываемый в память, не будет изменен другими процессами в течение интервала. над которым будет использоваться карта. См. Этот Почта для получения дополнительной информации.
Если файл текстовый, и вы хотите получить текст построчно, проще всего использовать 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). Пожалуйста, не используйте это решение на практике или используйте модифицированную версию с мультипликативным ростом.
realloc () может расширить существующую память до нового размера, не копируя старую память в новую большую часть памяти. только если есть промежуточные вызовы malloc (), потребуется переместить память и сделать это решение O (n ^ 2). здесь нет вызовов malloc (), которые происходят между вызовами realloc (), поэтому решение должно быть в порядке.
Вы можете читать прямо в буфер «str» (с соответствующим смещением), без необходимости копировать из промежуточного «buf». Однако этот метод обычно приводит к чрезмерному выделению памяти, необходимой для содержимого файла. Также следите за двоичными файлами, printf не будет обрабатывать их правильно, и вы, вероятно, все равно не захотите печатать двоичные файлы!
// 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;
}
Это довольно грубое решение, потому что ничего не проверяется на нулевое значение.
Это будет только с дисковыми файлами. Он не будет работать для именованных каналов, стандартного ввода или сетевых потоков.
Ха, и зачем я сюда приехал! Но я думаю, что вам нужно либо завершить строку нулем, либо вернуть длину, которую необязательно принимает glShaderSource.
Если вы используете 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 Такой быстрый ответ на вопрос девять лет назад, когда я редактирую! Хотя функциональная часть - это чистый C, я прошу прощения за свой ответ will-not-run-on-c.
Этот древний вопрос был включен в список активных вопросов. Я не искал.
Этот код приводит к утечке памяти, не забудьте освободить память malloc'd :)
Примечание. Это модификация принятого выше ответа.
Вот способ сделать это с проверкой ошибок.
Я добавил средство проверки размера, чтобы закрыть его, когда файл был больше 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()?
если ошибки не произошло - бесплатно (f_data); должен называться. спасибо за указание на это
Я добавлю свою версию, основанную на ответах здесь, просто для справки. Мой код учитывает 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.
«Самый простой способ» и «наименее подверженный ошибкам» часто противоположны друг другу.