Файловый ввод-вывод
Ограничения вычислений в оперативной памяти
Внешняя память и файлы
Представление данных в файле
Унификация модели: память и файл
Абстракция файла
Потоки в языке C
Тип FILE и его роль
Стандартные потоки
Особенности stdin, stdout, stderr
Ввод-вывод в стандартные потоки
Потоки как уровень абстракции
Переносимость и реальные системы
Открытие и закрытие файла на диске
Чтение и запись файла на диске
Операции над файловыми потоками
Модель операций ввода-вывода
Символьный и построчный ввод-вывод
Чтение и запись символов и строк
Форматированный ввод-вывод
Пример – текстовый ввод-вывод структуры
Обработка ошибок при работе с файлами
Пример: подсчёт количества строк в файле
Текстовый ввод-вывод
Текстовый поток
Преобразования в текстовом режиме
Потеря точности при работе с числами
Условия корректного восстановления данных
Ограничения текстового формата
Бинарный ввод-вывод
Бинарный ввод-вывод
Основные функции
Пример: чтение и запись двоичных чисел
Преимущества и ограничения
Применение бинарных файлов
Преобразование Little Endian <-> Big Endian
Загрузка и сохранение точек в Little Endian-формате
Загрузка и сохранение точек
Потоки и позиция в файле
Потоки и позиция в файле
Управление позицией
Где применяется позиционирование?
Перезапись части файла
Буферизация потоков
Буферизация потоков
Сброс буфера
Проверка fclose
Пример: два потока
Конец файла
Конец файла (EOF)
EOF и ошибки чтения
Итоги
231.19K
Категория: ПрограммированиеПрограммирование

File IO

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

2. Ограничения вычислений в оперативной памяти

• Время жизни данных
• Объекты, размещённые в оперативной памяти (RAM),
существуют только в пределах времени выполнения
процесса
• После завершения процесса его адресное пространство
уничтожается
• Программы, работающие только с RAM, не могут:
• сохранять результаты между запусками
• обрабатывать объёмы данных, превышающие доступную
память
• обмениваться данными с другими процессами через
устойчивое хранилище

3. Внешняя память и файлы

• Необходимость внешнего
хранения
• Для преодоления ограничений RAM
требуется механизм
долговременного хранения данных
вне процесса
• Файл — это именованная
последовательность байтов,
размещённая во внешней памяти
(диск, SSD и т.п.)
• Модель взаимодействия
• Данные не обрабатываются
непосредственно «в файле»
• Программа выполняет перенос
данных между адресным
пространством процесса и внешней
памятю (файлом)
Процесс в памяти
Адресное
пространство
Буфер в памяти

4. Представление данных в файле

• Содержимое файла не содержит информации о типах
данных
• содержимое файла рассматривается программой и ОС как
последовательность байтов (в старых ФС тип мог храниться
как метаданные)
• Их интерпретация полностью определяется программой.
• Один и тот же набор байтов может быть
интерпретирован как:
• целое число
• число с плавающей точкой (float)
• структура
• машинная инструкция
• Тип данных определяется способом интерпретации, а
не самим байтовым представлением

5. Унификация модели: память и файл

• Оперативная память — адресуемое пространство
байтов
• Файл — линейная последовательность байтов
Свойство
ОЗУ
Файл
Доступ
произвольный (по
адресу)
последовательный / с
позицией
Модель доступа
указатели
Смещение в файле
Скорость
высокая
существенно ниже
Время жизни
время выполнения
процесса
долговременное хранение
• Различие между RAM и файлом заключается не в
представлении данных, а в механизме доступа и
физических характеристиках хранения

6. Абстракция файла

7. Потоки в языке C

• В языке C ввод-вывод осуществляется через потоки
(streams)
• Поток (stream) — это абстракция для работы с
последовательностью данных, поступающих из
источника или направляемых в приёмник
• Примеры источников и приёмников
• файлы на диске
• терминал (stdin / stdout)
• сокеты
• устройства (клавиатура, принтер и др.)
• Ввод-вывод в C унифицирован: разные устройства
представлены одинаково — как потоки

8. Тип FILE и его роль

• Поток в C представлен типом FILE *
• Свойства FILE
• непрозрачный (opaque) тип
• структура скрыта от программиста
• управляется стандартной библиотекой
• Что содержит FILE (концептуально)
• текущую позицию в потоке
• буфер ввода/вывода
• состояние ошибок
• признак конца файла (EOF)
• Ограничение
• Объекты типа FILE не создаются напрямую
• Используются только через API (fopen, fread, …)

9. Стандартные потоки

• В программе всегда доступны три стандартных
потока
• stdin — стандартный ввод
• stdout — стандартный вывод
• stderr — поток ошибок
• Использование
• printf("Hello\n");
// → stdout
fprintf(stderr, "Error\n"); // → stderr
scanf("%d", &x);
// ← stdin
• Стандартные потоки — это обычные FILE*

10. Особенности stdin, stdout, stderr

• Буферизация
• stdout — обычно буферизован
• stderr — как правило, не буферизован
• Назначение
• stdout → нормальный вывод
stderr → сообщения об ошибках
• Позволяет разделять данные и ошибки
• Пример
• ./program < in.txt > out.txt 2> err.txt

11. Ввод-вывод в стандартные потоки

#include <stdio.h>
int main(void) {
int a, b;
if (fscanf(stdin, "%d %d", &a, &b) != 2)
{
fprintf(stderr, "Input error: expected two integers\n");
return 1;
}
fprintf(stdout, "Sum = %d\n", a + b);
}
./program < input.txt > out.txt 2> err.txt

12. Потоки как уровень абстракции

• Уровень абстракции стандартной библиотеки
• stdio.h предоставляет универсальный интерфейс для
работы с различными источниками данных
• Имеем одинаковый API для файлов, терминала,
устройств
• Цена абстракции
• Интерфейс является обобщённым и ограниченным
• Примеры ограничений
• нет понятия каталогов
• нет прямого доступа к файловой системе
• поведение может зависеть от платформы

13. Переносимость и реальные системы

• Стандарт C обеспечивает переносимость, но не
полноту возможностей
• Для реальных задач используются:
• POSIX API (Unix/Linux)
• WinAPI (Windows)
• платформенно-зависимые расширения
• stdio.h — это базовый уровень, а не полный
интерфейс файловой системы
• Язык C определяет абстракцию, а не конкретную
реализацию ввода-вывода

14. Открытие и закрытие файла на диске

• Открытие файла
• FILE *f = fopen("file.txt", "mode");
• Часто используемые режимы:
• “r“ - открыть для чтения
• “w” – открыть для записи
• “a” – добавление в конец файла
• “rb” / “wb” открыть в двоичном режиме
• Возврат NULL – признак ошибки
• Закрытие файла
• fclose(f)
• После fclose поток становится недействительным

15. Чтение и запись файла на диске

int main(void) {
// Для записи текста в файл, необходимо открыть его в режиме "w" (write).
FILE* f = fopen("file.txt", "w");
if (f == NULL) {
// Если fopen возвращает NULL, значит произошла ошибка при открытии файла.
perror("Failed to open file.txt");
return 1;
}
fprintf(f, "I love C!\n");
First line: I love C!
fprintf(f, "How do you do?\n");
Second line: How do you do?
fclose(f);
f = fopen("file.txt", "r"); // Проверка ошибок пропущена для экономии места
char buf[64];
if (fgets(buf, sizeof(buf), f) != NULL) { printf("First line: %s", buf); }
if (fgets(buf, sizeof(buf), f) != NULL) { printf("Second line: %s", buf); }
fclose(f);
}

16. Операции над файловыми потоками

17. Модель операций ввода-вывода

• Последовательность работы
• открытие → операции чтения/записи → закрытие
• Характер операций
• ввод-вывод осуществляется последовательно
• каждая операция изменяет позицию в файле
• данные проходят через буфер библиотеки
• Обработка ошибок
• fopen → проверка на NULL
• fprintf / fscanf → проверка возвращаемого значения
• ferror, feof
• Ввод-вывод — это управляемое перемещение данных
между потоком и памятью программы

18. Символьный и построчный ввод-вывод

Символьный и построчный вводвывод
• Посимвольный ввод-вывод
• int fgetc(FILE *f);
int fputc(int c, FILE *f);
• fgetc — чтение одного символа
• fputc — запись одного символа
• Построчный ввод-вывод
• char *fgets(char *s, int size, FILE *f);
int
fputs(const char *s, FILE *f);
• fgets — чтение строки в буфер (сохрнаяет '\n', если он
был прочитан)
• fputs — запись строки в поток (не добавляет '\n'
автоматически)
• Когда используются
• fgetc / fputc — обработка данных посимвольно
• fgets / fputs — работа со строками целиком

19. Чтение и запись символов и строк

#include <stdio.h>
int c = fgetc(f);
char buf[16];
if (fgets(buf, sizeof(buf), f) == NULL) {
perror("fgets");
fclose(f);
return 1;
}
fclose(f);
int main(void) {
FILE* f = fopen("example.txt", "w");
if (f == NULL) {
perror("fopen");
return 1;
}
fputs("Hello", f);
fputc('!', f);
fclose(f);
printf("First char: %c\n", c);
printf("Rest: %s\n", buf);
}
f = fopen("example.txt", "r");
if (f == NULL) {
perror("fopen");
return 1;
}
First char: H
Rest: ello!

20. Форматированный ввод-вывод

• Запись форматированных данных
• fprintf(f, "%d %f\n", x, y);
• Чтение форматированных данных
• fscanf(f, "%d %f", &x, &y);
• Особенности
• аналогичны printf / scanf
• выполняют преобразование данных ↔ текст
• работают с форматными строками
• Пример представления
• 42 3.14
• в файле хранится текст, а не бинарное представление

21. Пример – текстовый ввод-вывод структуры

typedef struct {
double x;
double y;
} Point;
int main(void) {
FILE* f = fopen("point.txt", "w");
if (f == NULL) {
perror("Open error");
return 1;
}
typedef struct { … } Point;
int main(void) {
FILE* f = fopen("point.txt", "r");
if (f == NULL) {
perror("Open failed");
return 1;
}
Point p;
if (fscanf(f, "%lf %lf", &p.x, &p.y) != 2) {
fprintf(stderr, "Read error\n");
fclose(f);
return 1;
}
fclose(f);
printf("Point: (%f, %f)\n", p.x, p.y);
Point p = { 1.5, -2.3 };
fprintf(f, "%f %f\n", p.x, p.y);
fclose(f);
}
}

22. Обработка ошибок при работе с файлами

• Любая операция ввода-вывода может завершиться ошибкой
• файл не существует
• недостаточно прав доступа
• ошибки устройства ввода-вывода
• некорректный формат данных
• Открытие файла
• fopen возвращает NULL
• Проверка операций ввода-вывода
• fprintf может вернуть отрицательное значение
• fscanf возвращает количество прочитанных элементов
• Дополнительные средства
• ferror(f) — проверка ошибки потока
• feof(f) — достижение конца файла

23. Пример: подсчёт количества строк в файле

int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr,"Usage: %s <file>\n",
argv[0]);
return 1;
}
if (ferror(f)) {
perror("fgetc");
fclose(f);
return 1;
}
if (fclose(f) == EOF) {
perror("fclose");
return 1;
}
FILE *f = fopen(argv[1], "r");
if (f == NULL) {
perror("fopen");
return 1;
}
printf("Number of lines: %ld\n", lineCount);
}
int c;
long lineCount = 0;
while ((c = fgetc(f)) != EOF)
if (c == '\n’) lineCount++;

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

25. Текстовый поток

• Текстовый поток — это последовательность
символов, организованная в строки
• строка = 0 или более символов + завершающий '\n'
• наличие '\n' в последней строке — зависит от
реализации
• Представление строк зависит от операционной
системы
• Unix: \n
• Windows: \r\n
• Следствие
• При вводе/выводе в текстовый поток возможны
преобразования символов

26. Преобразования в текстовом режиме

• Модель ввода-вывода
• данные в памяти ↔ текстовое представление ↔ поток
• Возможные преобразования
• добавление символов
• удаление символов
• замена символов
• Пример (Windows)
• '\n' → '\r\n'
'\r\n' → '\n'
(запись)
(чтение)
• Ключевое следствие
• Текстовый поток не гарантирует побайтовую идентичность
данных

27. Потеря точности при работе с числами

int main(void) {
FILE* f = fopen("pi.txt", "w");
if (f == NULL) {
perror("fopen");
return 1;
}
if (f == NULL) {
perror("fopen");
return 1;
}
double pi1;
if (fscanf(f, "%lf", & pi1) != 1) {
fprintf(stderr, "Ошибка чтения\n");
fclose(f);
return 1;
}
const double pi = 3.14159265358979323846;
fprintf(f, "%f\n", pi);
if (fclose(f) == EOF) {
perror("fclose");
return 1;
}
fclose(f);
printf(“pi = %.15f\n", pi);
printf(“pi1 = %.15f\n", pi1);
printf("difference = %.15f\n", pi - pi1);
f = fopen("pi.txt", "r");
}
pi = 3.141592653589793
pi1 = 3.141593000000000
difference = -0.000000346410207

28. Условия корректного восстановления данных

• Данные при чтении совпадут с записанными только
при выполнении условий
• используются только печатаемые символы, '\t' и '\n'
• перед '\n' отсутствуют пробельные символы
• последний символ — '\n'
• Нарушение этих условий может изменить данные
при чтении

29. Ограничения текстового формата

• Свойства текстового ввода-вывода
• данные проходят через текстовое представление
• возможны платформенно-зависимые преобразования
• отсутствует побайтовая идентичность
• Ограничения
• преобразование символов (например, '\n' ↔ "\r\n")
• зависимость от формата записи и чтения
• возможная потеря или изменение данных
• потеря точности чисел с плавающей точкой
• Текстовый ввод-вывод не гарантирует точного
восстановления исходного представления данных
• Он удобен для человека, но не подходит для точного
хранения и передачи данных

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

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

• Основная идея
• байты в памяти те же, что и байты в файле
• Свойства
• отсутствует текстовое представление
• нет преобразований при вводе/выводе
• данные копируются побайтно
• Следствие
• Файл содержит точное представление данных в памяти

32. Основные функции

• Запись
• size_t fwrite(const void *ptr, size_t size, size_t
count, FILE *f);
• Чтение
• size_t fread(void *ptr, size_t size, size_t count,
FILE *f);
• Интерпретация параметров
• ptr — указатель на данные
• size — размер одного элемента
• count — количество элементов

33. Пример: чтение и запись двоичных чисел

int main(void) {
FILE* f = fopen("pi.bin", "wb");
if (f == NULL) {
perror("fopen");
return 1;
}
const double pi = 3.14159265358979323846;
if (fwrite(&pi, sizeof(pi), 1, f) != 1) {
perror("fwrite");
goto err;
}
fclose(f);
f = fopen("pi.bin", "rb");
if (f == NULL) {
perror("fopen");
return 1;
}
double pi1;
if (fread(&pi1, sizeof(pi1), 1, f) != 1) {
perror("fread");
goto err;
}
fclose(f);
printf("pi = %.15f\n", pi);
printf("pi1 = %.15f\n", pi1);
printf("memcmp: %d\n",
memcmp(&pi1, &pi, sizeof(pi)));
return 0;
err:
fclose(f);
return 1;
}
pi = 3.141592653589793
pi1 = 3.141592653589793
memcmp: 0

34. Преимущества и ограничения

• Преимущества
• точное сохранение данных
• отсутствие потери точности
• высокая производительность
• простота для структур
• Ограничение – зависимость от платформы:
• порядок байтов (endianness)
• размеры типов
• выравнивание и padding
• Бинарный файл привязан к представлению данных в
памяти

35. Применение бинарных файлов

• Типичные области использования
• графические форматы (PNG, JPEG, BMP)
• аудио и видео файлы (MP3, WAV, MP4)
• архивы (ZIP, TAR)
• базы данных и их файлы хранения
• Системное программирование
• исполняемые файлы
• образы памяти (memory dumps)
• сетевые протоколы
• Бинарные файлы используются, когда требуется точное,
компактное и эффективное хранение данных
• Ограничения
• зависимость от платформы
• необходимость строгого формата
• нечитаемость человеком

36. Преобразование Little Endian <-> Big Endian

Преобразование Little Endian
<-> Big Endian
// Converts a 32-bit unsigned integer
// to a 4-byte array in little-endian format
void U32ToLE(uint32_t x, uint8_t out[4]) {
out[0] = (uint8_t)(x & 0xFF);
out[1] = (uint8_t)((x >> 8) & 0xFF);
out[2] = (uint8_t)((x >> 16) & 0xFF);
out[3] = (uint8_t)((x >> 24) & 0xFF);
}
// Converts a 32-bit unsigned integer
// to a 4-byte array in big-endian format
void U32ToBE(uint32_t x, uint8_t out[4]) {
out[0] = (uint8_t)((x >> 24) & 0xFF);
out[1] = (uint8_t)((x >> 16) & 0xFF);
out[2] = (uint8_t)((x >> 8) & 0xFF);
out[3] = (uint8_t)(x & 0xFF);
}
// Converts a 4-byte array in little-endian
// format to a 32-bit unsigned integer
uint32_t LEToU32(const uint8_t in[4]) {
return (uint32_t)in[0]
| ((uint32_t)in[1] << 8)
| ((uint32_t)in[2] << 16)
| ((uint32_t)in[3] << 24);
}
// Converts a 4-byte array in big-endian
// format to a 32-bit unsigned integer
uint32_t BEToU32(const uint8_t in[4]) {
return ((uint32_t)in[0] << 24)
| ((uint32_t)in[1] << 16)
| ((uint32_t)in[2] << 8)
| (uint32_t)in[3];
}

37. Загрузка и сохранение точек в Little Endian-формате

typedef struct
{
int x;
int y;
} Point;
typedef struct
{
int32_t x;
int32_t y;
} Point32;
bool SavePointToFile(Point p, FILE* f) {
static_assert(sizeof(Point) == sizeof(Point32));
Point32 p32;
U32ToLE((uint32_t)p.x, (uint8_t*)&p32.x);
U32ToLE((uint32_t)p.y, (uint8_t*)&p32.y);
return fwrite(&p32, sizeof(p32), 1, f) == 1;
}
bool LoadPointFromFile(Point* p, FILE* f) {
static_assert(sizeof(Point) == sizeof(Point32));
Point32 p32;
if (fread(&p32, sizeof(p32), 1, f) != 1)
return false;
p->x = (int)LEToU32((uint8_t*)&p32.x);
p->y = (int)LEToU32((uint8_t*)&p32.y);
return true;
}

38. Загрузка и сохранение точек

int main(void) {
Point p = { .x = 123456789, .y = -987654321 };
FILE* f = fopen("point.bin", "wb");
if (f == NULL) {
perror("fopen");
return 1;
}
if (!SavePointToFile(p, f)) {
perror("fwrite");
goto err;
}
fclose(f);
f = fopen("point.bin", "rb");
if (f == NULL) {
perror("fopen");
return 1;
}
Point p1;
if (!LoadPointFromFile(&p1, f)) {
perror("fread");
goto err;
}
fclose(f);
printf("Point: (%d, %d)\n", p1.x, p1.y);
return 0;
err:
fclose(f);
return 1;
}
Point: (123456789, -987654321)

39. Потоки и позиция в файле

40. Потоки и позиция в файле

• Файл — это последовательность байтов
• Текущая позиция
• каждый поток имеет текущую позицию (offset)
• операции чтения/записи используют эту позицию
• после операции позиция сдвигается
• Пример
• int c = fgetc(f);
• читается байт из текущей позиции
• позиция увеличивается на 1
• Ключевая идея
• Поток ведёт себя как указатель, движущийся по
массиву байтов

41. Управление позицией

• Основные функции
• fseek(f, offset, origin);
• ftell(f);
• rewind(f);
• Параметры fseek
• offset — смещение
• origin:
• SEEK_SET — от начала файла
• SEEK_CUR — от текущей позиции
• SEEK_END — от конца файла
• Пример
• fseek(f, 100, SEEK_SET); → перейти к 100-му байту файла
• Назначение
• Произвольный доступ к данным в файле

42. Где применяется позиционирование?

• Основная область применения
- бинарные файлы
• файл рассматривается как массив байтов
• данные имеют фиксированную структуру
• можно вычислить смещение
• Пример
• N-й элемент → offset = N * sizeof(element)
• Ограничения для текстовых файлов:
• возможны преобразования символов
• позиция может не соответствовать «символам»
• поведение fseek ограничено стандартом
• Перемещение по файлу имеет смысл прежде всего для
бинарных данных

43. Перезапись части файла

int main(void) {
uint32_t a[5] = { 10, 20, 30, 40, 50 };
uint32_t b[5];
FILE* f = fopen("numbers.bin", "w+b");
fwrite(a, sizeof(uint32_t), 5, f);
fseek(f, 3 * sizeof(uint32_t), SEEK_SET);
uint32_t x = 42;
fwrite(&x, sizeof(uint32_t), 1, f);
fseek(f, 0, SEEK_SET);
fread(b, sizeof(uint32_t), 5, f);
fclose(f);
printf("Modified array: ");
for (size_t i = 0; i < 5; ++i) {
printf("%u ", b[i]);
}
printf("\n");
}
Modified array: 10 20 30 42 50

44. Буферизация потоков

45. Буферизация потоков

• Что происходит при вводе-выводе
• программа → буфер → файл
• Особенности
• операции fprintf, fwrite не обязательно сразу пишут
в файл
• данные накапливаются во внутреннем буфере
• запись происходит пакетами
• Причина
• Буферизация повышает производительность
• меньше системных вызовов
• эффективнее работа с устройством

46. Сброс буфера

• Данные могут оставаться в буфере и не попадать в
файл сразу
• Решение
• fflush(f);
• Что делает fflush
• принудительно записывает буфер в файл
• гарантирует, что данные переданы системе
• Когда используется
• когда данные нужно сделать видимыми немедленно
• при отладке
• при работе с критическими данными
• fclose автоматически выполняет fflush

47. Проверка fclose

• Ошибки записи могут проявиться только при fclose
• Неправильно
• fwrite(data, size, count, f);
fclose(f);
// результат игнорируется
• Правильно
• fwrite(data, size, count, f);
if (fclose(f) == EOF) {
perror("fclose");
}
• Причина
• данные сначала записываются в буфер
• фактическая запись может происходить позже
• ошибка может возникнуть при сбросе буфера
• Проверка fclose обязательна для корректной обработки ошибок
ввода-вывода

48. Пример: два потока

int main(void) {
const char* filename = "test.txt";
fflush(fw); // синхронизация
rewind(fr); // вернуться в начало
FILE* fw = fopen(filename, "w"); // поток записи
FILE* fr = fopen(filename, "r"); // поток чтения
if (fgets(buf, sizeof(buf), fr) != NULL) {
printf("Read after fflush: %s", buf);
}
if (!fw || !fr) {
perror("fopen");
return 1;
}
fclose(fw);
fclose(fr);
fprintf(fw, "Hello\n");
}
char buf[64];
if (fgets(buf, sizeof(buf), fr) == NULL) {
printf("Read before fflush: EOF\n");
}
Read before fflush: EOF
Read after fflush: Hello

49. Конец файла

50. Конец файла (EOF)

• EOF (End Of File) — признак достижения конца
потока
• Как возникает
• чтение дошло до конца файла
• дальнейшие операции чтения невозможны
• Пример
• int c;
while ((c = fgetc(f)) != EOF) {
// обработка
}
• EOF — это не символ, а специальное значение

51. EOF и ошибки чтения

• Проблема
• EOF может означать: конец файла или ошибку чтения
• Проверка
• if (ferror(f)) {
// ошибка
}
if (feof(f)) {
// достигнут конец файла
}
• Типичная ошибка
• while (!feof(f)) {
fread(...); // ❌ неверно
}
• Правильный подход – проверять результат операции чтения

52. Итоги

• Модель
• файл = последовательность байтов
• ввод-вывод = перенос данных
• Подходы
• текстовый → преобразования
• бинарный → точное представление
• Механизмы
потоки (FILE*)
стандартные потоки (stdin, stdout, stderr)
буферизация
позиция в файле
• Практика
• проверка ошибок
• согласованный формат
• учёт endianness
• Файл и память — это байты. Различие — в доступе и хранении.
English     Русский Правила