Перейти к основному содержимому

Файловый ввод-вывод

Разработчику Архитектору

Файловый ввод-вывод

Программа на С взаимодействует с диском через потоки (streams) из <stdio.h> — абстракцию «последовательность байт с буфером». Консоль — те же потоки: stdin (ввод), stdout (вывод), stderr (ошибки).

Файл на диске открывают функцией fopen; она возвращает указатель FILE * — дескриптор потока в памяти программы. Закрытие fclose сбрасывает буферы и освобождает ресурс ОС.

Подробные сигнатуры — в справочнике. Ошибки — по идиомам.

Термины

ТерминОбъяснение
FILE *объект потока; не путать с «файлом» на диске
Текстовый режимвозможны преобразования \n\r\n (Windows)
Бинарный режим ("rb")байты как есть
Буферизациябиблиотека копит данные пачками для скорости
EOFконец файла при чтении

Открытие и закрытие

#include <stdio.h>

FILE *f = fopen("data.txt", "r");
if (f == NULL) {
perror("data.txt");
return 1;
}

/* ... */

fclose(f);

Режимы второго аргумента:

РежимНазначение
"r"чтение, файл должен существовать
"w"запись, создать или обнулить
"a"дозапись в конец
"rb", "wb"бинарный (важно на Windows)
"r+", "w+"чтение и запись

fopen возвращает NULL при ошибке; детали — errno и perror/strerror.

Разбор строки:

FILE *f = fopen("data.txt", "r");
  • "data.txt" — путь относительно текущей рабочей директории процесса (откуда запустили программу), если не указан полный путь;
  • "r" — только чтение; файл должен существовать;
  • f — либо валидный поток, либо NULL.

Текстовый ввод-вывод

Построчное чтение:

char line[256];
while (fgets(line, sizeof line, f) != NULL) {
/* line может содержать '\n' */
}

fgets ограничивает длину — защита от переполнения буфера. Не путать с небезопасным gets (удалён из стандарта).

Возвращаемое значение fgets:

  • указатель line — строка прочитана (может включать \n в конце);
  • NULL — конец файла или ошибка; отличить: feof(f) и ferror(f).
if (fgets(line, sizeof line, f) == NULL) {
if (ferror(f))
perror("read");
/* feof — нормальный конец файла */
}

Форматированный вывод:

fprintf(f, "id=%d name=%s\n", id, name);

Форматированный ввод fscanf удобен для прототипов; для ненадёжных внешних файлов лучше парсить вручную или по шаблону с проверкой каждого поля.

Запись символа/строки: fputc, fputs.


Бинарный ввод-вывод

В бинарном режиме байты передаются без преобразования перевода строк. Блоковые операции:

size_t n = fread(buffer, 1, sizeof buffer, f);
if (n == 0 && ferror(f)) { /* ошибка чтения */ }

size_t w = fwrite(data, elem_size, count, f);

fread/fwrite возвращают число успешно обработанных элементов (не байт, если elem_size > 1). Всегда сравнивать с ожидаемым count.

Пример: прочитать ровно 100 байт в буфер:

size_t got = fread(buf, 1, 100, f);
if (got != 100) {
if (ferror(f)) { /* ошибка диска */ }
else if (feof(f)) { /* файл короче 100 байт */ }
}

На Windows для бинарных форматов (картинки, .db) режим "rb" / "wb" обязателен — иначе байт 0x1A может интерпретироваться особым образом в текстовом режиме.


Буферизация

Библиотека буферизует поток для производительности. Режимы: полная буферизация, построчная (терминал), без буфера.

fflush(f); /* сбросить буфер записи на диск/ОС */
setvbuf(f, ...); /* настроить при открытии */

При аварийном завершении без fclose/fflush часть данных может остаться в буфере. Для критичных записей — fflush после важных блоков или открытие с немедленной записью.


Позиционирование

fseek(f, 0, SEEK_END);
long size = ftell(f);
rewind(f);

fseek(f, offset, SEEK_SET);

Для файлов больше 2 ГБ на 32-битных платформах используют fseeko/ftello (POSIX). Случайный доступ удобен для индексированных форматов и обновления записи фиксированной длины.


Сериализация структур

Запись struct целиком через fwrite(&obj, sizeof obj, 1, f) не переносима между компиляторами, архитектурами и версиями из-за выравнивания и порядка байтов (endianness).

Переносимые варианты:

  • поля по одному в известном порядке и фиксированных типах (uint32_t из <stdint.h>);
  • текстовые форматы (CSV, JSON через внешнюю библиотеку);
  • явный бинарный протокол с магическим числом и версией заголовка.

Для учебных проектов на одной машине допустим «сырой» дамп, если файл не уходит на другую платформу.


Строки как потоки — sprintf / sscanf

Форматирование в память:

char buf[64];
snprintf(buf, sizeof buf, "value=%d", x);

int x;
sscanf(buf, "value=%d", &x);

snprintf ограничивает размер — предпочтительнее sprintf. Аналогично для разбора — проверять код возврата sscanf (число сопоставленных полей).


Связь с другими темами

См. также: Память процесса, Преобразование в исполняемый файл.


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).