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

XRT — не «ещё одна библиотека утилит», а систематическая кроссплатформенная C-инфраструктура: от памяти, ошибок, контейнеров и корутин до сетевого движка с пятью бэкендами, собственного TLS и ядра протоколов HTTP/WebSocket. Первый урок при изучении XRT — не зубрёжка API, а понимание двухслойной структуры «ядро + расширения»: именно она определяет, на каком слое стоит каждая ваша строка. Глава сначала строит общую картину, затем завершает всю подготовку от нуля до компилируемости: получить исходники, изучить каталоги, понять два способа интеграции, скомпилировать первую программу и взглянуть на мощь обрезки возможностей. Прочитав главу, вы получите среду разработки, готовую к свободным экспериментам.

2.1引入

Предположим, вам нужно написать на C небольшой сервис, читающий файл конфигурации и пересылающий данные. По пути разрозненных библиотек вам понадобятся: библиотека разбора JSON, HTTP-клиент, библиотека TLS, пул потоков, assorted контейнеры… Они от разных авторов, со своими стилями обработки ошибок (коды возврата, errno, setjmp, abort), своими соглашениями о памяти (кто выделил, тот освобождает; подсчёт ссылок; GC-макросы) и своими системами сборки (CMake, Meson, самописные Makefile). Время на «сшивку» быстро превышает саму бизнес-логику, а любое обновление безопасности апстрима заставляет переоценивать всю цепочку зависимостей.

Ответ XRT — сделать эти возможности единым технологическим стеком: единая модель ошибок (глава 4), единые соглашения о памяти и владении (глава 5), единое соглашение о данных-представлениях (глава 3), пронизывающие всё снизу вверх до сетевого и протокольного уровней. Понимание этой унификации важнее запоминания любого API — именно эту панораму и строит глава.

Есть и скрытая цена, которую стоит назвать: пути обновления разрозненных зависимостей существуют каждый сам по себе — уязвимость в библиотеке TLS требует отдельной оценки, смена лицензии библиотеки JSON — отдельной миграции, покрытие тестами везде разное. Единый стек сводит всё к одному решению: подняли номер версии — и патчи безопасности со регрессионными тестами всего стека (в репозитории тысячи тестовых файлов) приходят вместе. Глава 8 расскажет о соглашениях версий и совместимости; том 12 — об управлении этим путём обновления в инженерии.

2.2概念

Ядро и расширения: строгая двухслойная структура

  • Ядро (src/ + include/xrt/, 91 публичный заголовок, сотни обрезаемых модулей): фундамент (core / error / atomic / memory), контейнеры, текст и данные, логирование и файлы/процессы, корутины и асинхронность, сетевой движок с пятью бэкендами (IOCP / epoll / kqueue / io_uring / select), собственный TLS 1.2/1.3 с X.509 и ядро протоколов HTTP/1.x и WebSocket.
  • Библиотеки расширений (extlibs/, пять независимых продуктов): xhttp (рантаймы HTTP-клиента/сервера), xws (рантайм WebSocket), xruntime (динамические типы и объектная система), xmail (полный набор почтовых протоколов), xssh (стек протокола SSH2).

Зависимости строго односторонние: расширения вызывают только ядро; ядро не ссылается на расширения ни разу. Тома 1–9 этой книги посвящены ядру; тома 10 и 11 — расширениям. Прямая выгода для пользователя: «возможности ядра самодостаточны» — напишите небольшой инструмент разбора конфигурации только на ядре, вообще не замечая расширений; а любая функция расширений, прослеженная до дна, — это уже изученные вами примитивы ядра. Порядок обучения складывается сам собой: после томов 1–9 документацию расширений вы читаете почти напрямую.

Одна легко путающаяся точка: ядро хранит ядро протоколов HTTP и WebSocket (разбор, кодирование, семантическая проверка), а высокоуровневые рантаймы — «клиент», «сервер», «маршрутизация», «пул соединений» — живут в расширениях xhttp / xws. Увидели xrtHttp1RequestParse — это ядро; увидели клиентские API xhttp — это расширение: разные стили префиксов, разные продукты.

Обзор структуры репозитория

После получения исходников в повседневной разработке вы чаще всего касаетесь лишь пяти каталогов:

КаталогСодержимоеРоль в книге
include/xrt/91 публичный заголовок ядра; экспортируемые функции несут XRT_API и комментарии-контракты«Первоисточник» каждой главы
single/Однозаголовочные артефакты xrt.h / xrt_decl.h (самодостаточная реализация всего ядра)База компиляции всех примеров книги
src/Реализация ядра и приватные контрактные заголовки src/internal/«Ключ ответов» при погружении в детали
extlibs/Пять расширений, каждое со своими src / include / tests / examples / singleУчебники томов 10 и 11
examples/Запускаемые примеры, организованные по модулям«Внешнее чтение» к каждой главе

Остальные каталоги: tools/ — цепочка инструментов сборки; tests/ — набор тестов (тому 12 посвящено их чтение); config/modules.json — манифест модулей, единый источник истины всей библиотеки; dev/ хранит бенчмарки и архивы.

Два способа интеграции

От исходников до исполняемого файла однозаголовочный путь — четыре шага:

1Выбрать модулиdefine XRT_MODULE_ALL или перечислить нужные модульные макросы
2Включить реализациюdefine XRT_IMPLEMENTATION — заголовок обрастает телами функций
3Подключить однозаголовочникinclude xrt.h (при компиляции -I указывает на каталог single/)
4Скомпилировать и слинковатьgcc main.c достаточно, никакие библиотечные файлы XRT не нужны

На инженерном этапе можно перейти на второй путь: инструментами сборки получить статическую или динамическую библиотеку либо включить исходники ядра в свою систему сборки с путями заголовков, единообразно <xrt/xxx.h>. Этот путь разворачивается в томе 12. Примеры книги единообразно идут однозаголовочным путём: его достоинство — «скопировал один файл и начал», а поведение обрезки видно как на ладони.

2.3示例

Первая полная программа

Вот первая полная программа книги. Она собирает фразу через xrtFormat (форматирование по правилам printf, возвращает управляемую XRT-строку), а по завершении вызывает xrtFree — эти два вызова уже задействуют единый сток памяти ядра:

/* hello.c —— первая программа книги */
#define XRT_MODULE_ALL
#define XRT_IMPLEMENTATION
#include <xrt.h>
#include <stdio.h>

int main(void)
{
	str s = xrtFormat("Hello, XRT %s!", xrtVersion());
	if ( s == NULL ) {
		return 1;
	}
	printf("%s\n", s);
	xrtFree(s);
	return 0;
}
$ gcc -O1 -I single hello.c -lws2_32 -liphlpapi$ ./a.exeHello, XRT 2.0.0-dev!

Что только что произошло. Две строки макросов стоят перед включением: XRT_MODULE_ALL выбирает включение всех модулей (можно перечислить только нужные — см. следующий раздел); XRT_IMPLEMENTATION заставляет эту единицу трансляции «обрасти» телами функций — в однозаголовочном режиме реализация и объявления живут в одном файле и переключаются этим макросом. Тип возвращаемого значения xrtFormat — str (управляемая XRT-строка; глава 3 раскрывает псевдонимы типов), и кто получил возвращаемое значение, тот и отвечает за xrtFree — это общебиблиотечное соглашение о владении, полностью раскрытое в главе 5.

Заодно обратите внимание на запись пути отказа: при неудаче выделения памяти xrtFormat возвращает NULL, программа проверяет и возвращает ненулевой код выхода. В реальной инженерии здесь ещё следовало бы прочитать слот ошибки потока и напечатать причину — это материал главы 4; здесь программа минимальна. Ещё одна деталь, которую стоит сделать привычкой прямо сейчас: возврат из main чего-то отличного от 0 означает неудачу; все примеры книги следуют соглашению «ноль — успех, не ноль — неудача», согласованному с направлением возвращаемых API XRT значений bool.

Флаги -lws2_32 -liphlpapi в команде сборки — сетевые системные библиотеки платформы Windows: хотя программа не пишет сетевого кода, сетевые модули ядра XRT ссылаются на них при включении всех модулей; Linux/macOS эти два флага не нужны. Эта команда будет повторяться на протяжении книги; глава 8 объяснит, почему после обрезки ссылаемые зависимости тоже уменьшаются.

Мощь обрезки: одна программа, два набора модулей

Замените первую строку макросов только нужными модулями, не меняя больше ни символа:

/* hello-min.c —— включаем только модуль string и его замыкание зависимостей */
#define XRT_MODULE_STRING
#define XRT_IMPLEMENTATION
#include <xrt.h>
#include <stdio.h>

int main(void)
{
	str s = xrtFormat("Hello, XRT %s!", xrtVersion());
	if ( s == NULL ) {
		return 1;
	}
	printf("%s\n", s);
	xrtFree(s);
	return 0;
}
$ gcc -O1 -I single hello.c -lws2_32 -liphlpapi$ gcc -O1 -I single hello-min.c -lws2_32 -liphlpapi$ ls -l a.exe a2.exe-rwxr-xr-x 1 admin 106496 a.exe ← XRT_MODULE_ALL-rwxr-xr-x 1 admin 24064 a2.exe ← XRT_MODULE_STRING(数值因工具链与版本而异)

Что только что произошло. XRT_MODULE_STRING включает только модуль строк; фундамент, от которого он зависит (core, error и др.), разворачивается автоматически замыканием зависимостей в features.h — перечислять вручную не нужно: «выбрал модуль — принеси всё, что ему нужно», в этом смысл замыкания. Различия в размере зависят от версии и инструментария; числа выше лишь иллюстративны. Запустите ls -l сами — и «поставка по потребности» станет осязаемой. Заметьте и вторую выгоду: обрезка — это не только экономия размера; ссылаемые системные библиотеки, таблица инициализируемых модулей и даже поверхность атаки сужаются вместе; при встраивании в другие небольшие инструменты все три пункта одинаково ценны. Полный механизм модульных макросов раскрывается в главе 8.

2.4契约

  • Односторонняя зависимость: расширения вызывают только ядро; в коде ядра не появится ссылок на xhttp/xws.
  • Соглашения однозаголовочных макросов: XRT_IMPLEMENTATION может встретиться ровно в одной единице трансляции проекта; модульные макросы должны быть определены до включения <xrt.h>.
  • Замыкание зависимостей: включение одного макроса семейства XRT_MODULE_ автоматически приносит все его зависимости; ручное перечисление чревато пропусками — доверьте это замыканию.
  • Версионная линия: макросы компиляции и рантайм-функция xrtVersion() из одного источника (демонстрация в главе 3); при интеграции со связываемой библиотекой ими сверяют совпадение версий заголовка и библиотеки.
  • Базлайн примеров: примеры книги используют корень репозитория как рабочий каталог и вывод Windows + GCC как эталон; на Linux/macOS просто уберите два флага линковки, совпадающее поведение повторно не оговаривается.

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

Ловушка 1: XRT_IMPLEMENTATION в заголовке или определён дважды:Ловушка 1: XRT_IMPLEMENTATION в заголовке или определён дважды

Симптомы: линковщик выдаёт лавину «multiple definition» (дублируются символы реализации XRT).

Причина: XRT_IMPLEMENTATION заставляет включающую единицу трансляции генерировать тела функций; две единицы с ним = две реализации.

错误写法
/* xrt_util.h —— ваш общий заголовок */
#define XRT_IMPLEMENTATION     /* включается из нескольких .c → несколько реализаций */
#include <xrt.h>
正确写法
/* xrt_util.c —— единственная единица реализации */
#define XRT_MODULE_ALL
#define XRT_IMPLEMENTATION
#include <xrt.h>
/* в xrt_util.h только #include <xrt.h> (без двух макросов) */
Ловушка 2: модульные макросы по памяти:Ловушка 2: модульные макросы по памяти

Симптомы: компилятор сообщает «implicit declaration», или компиляция проходит, но линковка сообщает о неопределённых символах — заголовок включён, а функций нет.

Причина: выбран неверный набор модульных макросов — включённые модули не покрывают используемые API. Запись имён макросов по памяти — верный путь к такой ошибке; в частности, фундамент ядра (core/error) присутствует всегда и отдельного модульного макроса не имеет — «добавить макрос фундамента» не существует.

错误写法
#define XRT_MODULE_JSON        /* включён только JSON — он не зависит от форматирования строк */
#define XRT_IMPLEMENTATION
#include <xrt.h>
/* ниже вызов xrtFormat → string не включён, линковка падает */
正确写法
#define XRT_MODULE_STRING      /* сверьтесь с include/xrt/features.h или шпаргалкой главы 8 */
#define XRT_IMPLEMENTATION
#include <xrt.h>

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

Базовое: запустить и изменить

Скомпилируйте и запустите hello.c; измените вывод на два приветствия (дважды вызовите xrtFormat — не забудьте два xrtFree).

Продвинутое: минимальный набор модулей

hello.c использует только форматирование и запрос версии. Обрежьте его до минимального набора модулей, при котором программа ещё компилируется, и запишите включённые макросы и размер бинарника. Подсказка: начните с XRT_MODULE_STRING и смотрите, чего ещё не хватает линковщику.

Вызов: сравнение трёх ступеней размера

Скомпилируйте одну и ту же программу с XRT_MODULE_STRING, XRT_MODULE_REGEX и XRT_MODULE_ALL; запишите три размера бинарников и объясните происхождение различий (что regex несёт сверх строк? во сколько раз все модули больше одного?). Критерий приёмки: таблица из трёх строк плюс два предложения анализа; ступень XRT_MODULE_ALL не должна быть меньше ступени одного модуля.

Итоги

ТемаКраткая справка
Двухслойная структураЯдро (include/xrt/) + пять расширений (extlibs/); зависимости строго односторонние
Три шага одним заголовкомМодульные макросы → XRT_IMPLEMENTATION → #include <xrt.h> (-I single)
Макрос реализацииОдин раз на весь проект, в единственном .c
ОбрезкаXRT_MODULE_* выбирает модули, замыкание зависимостей разворачивает автоматически; имена макросов по features.h
Управляемая строкаxrtFormat возвращает str; по завершении xrtFree
Граница протоколовЯдро HTTP/WS — модули http, http1, websocket; рантаймы — xhttp/xws

📖 Справочники API в этой главе:core (25 записей)string (92 записей)

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