Similar presentations:
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
• Файл и память — это байты. Различие — в доступе и хранении.
programming