Ориентация главы

Первая из трёх глав о файлах — магистраль дескрипторов: xrtOpen (права комбинацией флагов) → WriteFull/ReadFull (семантика «прочитать/записать полностью») → xrtSeek/Tell (позиционирование по трём базисам) → xrtClose. Семейство с суффиксом Full забирает в библиотеку заготовку повторов на случай короткого чтения/записи (та же семантика, что у абстракции IO главы 41 — здесь её прямая форма на файловых дескрипторах); ReadAtFull читает по абсолютному смещению, не тревожа курсор — специализированная поза для подсматривания в заголовок файла. Средняя глава (глава 46) — отображения, блокировки и атомарная запись; следующая (глава 47) — каталоги и обход: три главы складываются в полную карту файловых операций.

45.1引入

Файловый API стандартной библиотеки C — слой исторических отложений: строки режимов fopen (кто помнит наизусть, чем "rb+" отличается от "w+b"), короткая семантика read/write (возвращаемое значение требует сверять в цикле самому), кроссплатформенные различия поведения (капкан конвертации переводов строк в текстовом режиме). Каждый C-программист писал цикл while для fread и гадал о моменте fflush — это не ваша вина, это вина возраста интерфейса; задача главы — положить этот исторический багаж на стол раз и навсегда.

Файловый слой XRT впитывает ямы этих пятидесяти лет: права — комбинация флагов (чтение/запись/создание/усечение/добавление — пять флагов побитовым ИЛИ; читается с одного взгляда, и больше никогда не нужно выяснять, кто есть кто из "rb+" и "w+b"), семантика Full встроена (прочитать или записать полностью либо отказ — заготовка циклов исчезает), различия платформ сглажены (едина бинарная семантика, без конвертации переводов строк, ошибки идут структурным отчётом главы 4, а не загадками errno). xrtOpen возвращает дескриптор xfile — стыковка с абстракцией IO главы 41 (строка адаптера превращает его в xreader/xwriter).

45.2概念

Жизненный цикл дескриптора (сперва выучите магистраль)

1ОткрытиеxrtOpen(путь, флаги) - чтение/запись/создание/усечение/добавление комбинируются побитово
2Чтение-записьWriteFull/ReadFull - успех только при ровно N байтах; короткость - отказ
3ПозиционированиеSeek(XSEEK_START/CURRENT/END) + Tell - курсор на трёх базисах
4ДополнениеReadAtFull читает по абсолютному смещению - курсор не тронут, только для подсматривания
5ЗакрытиеxrtClose - возвращаемое значение проверяется всегда (вердикт сброса буферов выносится здесь)

Примеры комбинаций флагов: XFILE_READ | XFILE_WRITE (открыть чтение-запись без создания), XFILE_WRITE | XFILE_CREATE | XFILE_TRUNCATE (перестроить и записать — стандартная поза временных файлов). Создание и усечение разделены явно — аварии вида «открыл существующий файл и случайно опустошил его» при комбинациях флагов неслучайно произойти не могут.

Семантика Full в файловой версии (конец заготовок)

Две конкретные формы семантики Full главы 41 на файлах: WriteFull при полном диске — на середине записи кончилось место, отказ целиком, а слот ошибки хранит структурные подробности (объект ошибки главы 4 несёт диагностику); ReadFull при раннем EOF — из 100 запрошенных байтов пришло 80 и затем EOF, отказ («файл усечён» — настоящий сигнал сбоя, а не «сойдёт и 80»).

ReadAtFull: боковое чтение, не тревожа курсор (только для подсматривания)

Потоково читая большой файл, вы хотите заглянуть в его заголовок (проверка magic number) — обычный Read сдвинет курсор, а написание «прочитал и Seek обратно» и витиевато, и ошибочно. ReadAtFull(句柄, 绝对偏移, 缓冲, 长度) (дескриптор, абсолютное смещение, буфер, длина) выполняет боковое чтение, не двигая курсор (образец проверяет Tell-ом, что курсор по-прежнему на 3) — стандартная поза подсматривающей проверки и запросов к индексу.

Стык с абстракцией IO (напрямую или через адаптер)

xrtReaderFromFile адаптирует xfile в единый Reader главы 41 (у записи то же семейство) — файл входит в канал «один код сериализации в трёх средах» (память/файл/сеть). Выбор между прямым подключением (API этой главы) и адаптером (интерфейс IO): разовая операция с одним файлом короче напрямую; когда одна логика обязана стыковаться со многими пунктами назначения (память/файл/сеть) — через адаптер; сам выбор продолжает суждение о «трёх хозяевах» главы 41.

45.3示例

Полная программа: магистраль дескрипторов насквозь

Из образца репозитория examples/file/basic/main.c — создание, запись, позиционирование, боковое чтение, закрытие, удаление:

examples/file/basic/main.c源码 ↗
#include <stdio.h>

#include <xrt.h>























 */


/* 展示二进制文件最常用的一次创建、完整写入和完整读取。 */
int main(void)
{
	static const char sText[] = "xrt file";
	char arrText[sizeof(sText)];
	uint64 iPosition;
	xfile File = xrtOpen("xrt-file-example.tmp",
		XFILE_READ | XFILE_WRITE | XFILE_CREATE | XFILE_TRUNCATE);

	if ( File == NULL ) {
		return 1;
	}
	if ( !xrtWriteFull(File, sText, sizeof(sText), NULL) ||
		 !xrtSeek(File, 3, XSEEK_START, NULL) ||
		 !xrtReadAtFull(File, 0, arrText, sizeof(arrText), NULL) ||
		 !xrtTell(File, &iPosition) || (iPosition != 3u) ||
		 !xrtSeek(File, 0, XSEEK_START, NULL) ||
		 !xrtReadFull(File, arrText, sizeof(arrText), NULL) ) {
		(void)xrtClose(File);
		return 1;
	}
	printf("%s\n", arrText);
	if ( !xrtClose(File) ) {
		return 1;
	}
	return xrtFileDelete("xrt-file-example.tmp") ? 0 : 1;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/file/basic/main.c -lws2_32 -liphlpapixrt file

Что только что произошло. (1) xrtOpen(路径, 写|创建|截断) (путь, запись|создание|усечение) — комбинация флагов объявляет «перестроить и записать», без строк режимов. (2) WriteFull пишет весь отрезок (8 байтов записаны ровно — встроенная гарантия отказа при короткой записи). (3) После Seek к началу ReadFull читает назад — цикл запись-чтение замкнулся. (4) Боковое чтение ReadAtFull проверяет: Tell курсора по-прежнему 3 — подглядывание не тревожит потоковую позицию, и это число есть доказательство. (5) xrtClose проверяет возвращаемое значение — сбои сброса буферов (последняя партия при полном диске) вскрываются здесь, а не крушением в продакшене. (6) xrtFileDelete удаляет временный файл по пути — дескриптор уже закрыт; удаление по пути — независимая операция; двухшаговая зачистка завершает дело.

Полная программа: три среды через IO-адаптер

Из examples/io/file/main.c — одна логика сериализации над памятью и файлом:

examples/io/file/main.c源码 ↗



















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	static const char sPath[] = "xrt-io-example.bin";
	unsigned char arrData[7];
	xwriter* pWriter = xrtWriterOpen(sPath);   /* 拥有式:Destroy 关文件 */
	xreader* pReader;
	int iResult = 1;

	/* 写 7 字节 "xrt io\n" 后立即关闭(Destroy 含落盘检查)。 */
	if ( (pWriter == NULL) ||
		 !xrtWriterWriteFull(pWriter, "xrt io\n", sizeof(arrData), NULL) ||
		 !xrtWriterDestroy(pWriter) ) {
		return 1;
	}

	/* 重新打开读取:ReadFull 保证读满 7 字节,否则失败。 */
	pReader = xrtReaderOpen(sPath);
	if ( (pReader != NULL) &&
		 xrtReaderReadFull(pReader, arrData, sizeof(arrData), NULL) ) {
		fwrite(arrData, 1u, sizeof(arrData), stdout);
		iResult = 0;
	}
	xrtReaderDestroy(pReader);

	/* 范例产物随用随删,不污染工作目录。 */
	(void)xrtFileDelete(sPath);
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/io/file/main.c -lws2_32 -liphlpapixrt io

Что только что произошло. (1) Файловый адаптер оборачивает xfile в xwriter — код сериализации (написанный в главе 40) ложится на файл без единой правки. (2) Та же логика, что с адаптером памяти: продукты совпадают байт в байт — файловая сторона проверки обещания «трёх сред». (3) Возврат адаптера расслоён чисто: Destroy адаптера и Close файла каждый сам по себе — в согласии с дисциплиной заимствованной адаптации главы 41.

45.4契约

  • Комбинации флагов: чтение/запись/создание/усечение/добавление — пять флагов побитовым ИЛИ; создание и усечение разделены явно — нечаянного опустошения не будет.
  • Семантика Full: успех только при ровно N байтах; полный диск и ранний EOF отказывают целиком со структурной детализацией ошибки.
  • Три базиса курсора: XSEEK_START/CURRENT/END (перечисление xseek главы 3); Tell сообщает текущую позицию в байтах.
  • Боковое чтение: ReadAtFull по абсолютному смещению не двигает курсор — выделенный канал для подсматривающих проверок и запросов к индексу.
  • Проверка закрытия: возвращаемое Close обязательно к проверке — проблемы сброса буферов получают вердикт при закрытии.
  • Явное удаление: xrtFileDelete не зависит от дескриптора — удаление закрытого файла по пути, вне жизненного цикла дескриптора.

От примеров к инженерии: три хозяина файловых дескрипторов

Конфигурация и малые файлы: чтение-запись целиком одним духом — Open/ReadAll или Open/WriteFull/Close, три шага; разбор JSON-файлов главы 43... главы 32 использует его именно так.Потоковая обработка: большие файлы циклом по кускам (ReadFull по 4 КБ) — полная форма упражнения поблочного копирования; в паре с построчным читателем главы 41 — построчное сканирование.Произвольный доступ: ReadAtFull прыгает по индексу (файлы индексов, позиционирование в журнале по смещению) — не тревожащее курсор боковое чтение здесь масштабируется. Три хозяина отвечают трём режимам файлового доступа (целокупно/последовательный поток/случайные прыжки) — сперва режим доступа, потом API; изоморфно выбору контейнеров (три вопроса главы 23).

Классификация ошибок: три лица файлового сбоя (сортировочный стол стратегий восстановления)

У сбоев файловых операций три лица, и обработка у каждого своя — перепутаешь сортировку, и повтор, и отчёт пойдут вкривь. Класс доступа (нет/нет прав/занято): доложить пользователю и попросить исправить — повторы бессмысленны; класс пространства (диск полон/квота превышена): доложить эксплуатации — после чистки или расширения повтор возможен; класс целостности (подсматривание в усечённый файл/провал проверки): доложить разработчику — обычно это сигнал параллельной правки или порчи данных. Категории ошибок главы 4 в файловом отображении: XERR_NOT_FOUND/PERMISSION → класс доступа, системные коды XERR_IO → класс пространства, бизнес-валидация → класс целостности. Смысл классификации — стратегия восстановления: слепой повтор для класса доступа — трата, для класса целостности — маскировка.

Одна привычка: трёхшаговое завершение файла

Трёхшаговое завершение записи файла достойно стать привычкой: написали — Flush-семантика со сверкой Close (вердикт долговечности) → переименовать в окончательное имя (атомарная видимость: читатель видит либо старый файл, либо новый; упрощённая версия атомарной записи главы 46) → на пути отказа удалить временный файл (мусора не остаётся). Проблема прямой записи под окончательным именем: крушение на середине оставляет усечённый файл, который читатель примет за полные данные. У трёхшагового завершения ровно два состояния продукта (нет/полный) — состояние «частично записано» уничтожено структурно. Привычка применима к сохранению конфигурации, переключению журналов, обновлению кеша — глава 46 даст её полную промышленную версию (временный файл + атомарное переименование + семантика fsync).

Типичные ошибки

Ловушка 1: непроверенное возвращаемое Close (вердикт долговечности):Ловушка 1: непроверенное возвращаемое Close (вердикт долговечности)

Симптом: программа «успешно» завершилась, а хвост данных файла потерян — последний сброс буфера (полный диск, превышение квоты) молча проглочен.

Причина: Close — точка вердикта данных: у большинства реализаций сброс буферов происходит при закрытии; не проверить — расписаться за посылку, не заглянув внутрь.

错误写法
xrtClose(File);   /* возвращаемое значение отброшено - сброс не замечен */
return 0;
正确写法
if ( !xrtClose(File) ) {
	ReportIoError(Path);   /* в слоте ошибки главы 4 детали: полный диск/квота */
	return 1;
}
return 0;
Ловушка 2: чтение заголовка файла через Seek туда-обратно (утечка состояния курсора):Ловушка 2: чтение заголовка файла через Seek туда-обратно (утечка состояния курсора)

Симптом: при потоковом чтении большого файла «изредка продолжение с ошибочной позиции» — смещение обратного Seek посчитано неверно (или перепутано чтением параллельной ветви).

Причина: написание «подсмотреть — Seek-Read — Seek обратно» выставляет состояние курсора напоказ всей последующей логике — одна неверно посчитанная позиция расставляет всё не по местам.

错误写法
xrtSeek(File, 0, XSEEK_START);      /* прыгнуть в начало */
xrtReadFull(File, Magic, 4);         /* прочитать magic */
xrtSeek(File, SavedPos, XSEEK_START); /* прыгнуть назад - откуда SavedPos? ошибка - катастрофа */
正确写法
if ( !xrtReadAtFull(File, 0, Magic, 4) ) {   /* боковое чтение по абсолютному смещению */
	return false;
}
CheckMagic(Magic);   /* курсор так и не двинулся - потоковая позиция безопасна сама собой */

Практические задания

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

Пятью комбинациями флагов (только чтение/чтение-запись/перестройка/добавление/создание без усечения) выполните открытие-операцию-закрытие; зафиксируйте поведение каждой комбинации для случаев «файл есть» и «файла нет» — десять клеток, сперва бумага, потом измерения.

Продвинутое: поблочное копирование с проверкой (стандартное упражнение потокового хозяина)

Реализуйте copy(源, 目标) (источник, цель): цикл кусками по 4 КБ ReadFull/WriteFull (хвостовой блок — по фактически прочитанному объёму, обычным Read на финале); отпечатки Hash64 (глава 12) источника и цели сравните. Подсказка: последний блок короче 4 КБ — единственное место, где короткое чтение разрешено.

Вызов: нюхач magic-чисел (боковое чтение на практике)

Реализуйте sniff(路径) (путь): ReadAtFull боком читает первые 16 байтов, распознаёт magic-числа gzip (1F 8B)/PNG/ZIP и выводит вердикт; затем, не мешая, потоково читает весь файл, считая строки (доказательство, что курсор не потревожен подсматриванием). Критерии приёмки: по тестовому файлу на каждый из трёх форматов вердикт верен; число строк совпадает с версией прямого чтения без нюханья.

Итоги

ТемаКратко
Открытиефлаги комбинируются побитово (чтение/запись/создание/усечение/добавление); без шифроблокнота строк режимов
Семейство Fullпрочитать/записать полностью или отказ; полный диск и ранний EOF - отказ целиком
ПозиционированиеSeek по трём базисам (START/CURRENT/END, xseek главы 3) + запрос Tell
Боковое чтениеReadAtFull по абсолютному смещению, курсор не двинут (доказуемо Tell-ом) - только подсматривание/индекс
Закрытиевозвращаемое Close обязательно к проверке - вердикт долговечности; сбои вскрываются здесь, не крахом в продакшене
Три средыReader/WriterFromFile адаптируют в единый интерфейс главы 41
Три хозяинацелокупно (конфигурация, малые файлы) / последовательный поток (большие файлы кусками) / случайные прыжки (смещения индекса) - режим доступа прежде API
Сортировка ошибоккласс доступа → пользователю / класс пространства → эксплуатации / класс целостности → разработчику - сортировка прежде повтора
Трёхшаговое завершениеClose выносит вердикт → переименование атомарно видно → удаление временного файла на пути отказа - состояния частичной записи уничтожены структурно

📖 Справочники API в этой главе:file (132 записей)io (62 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки