Ориентация главы
В главе 2 вы вкомпилировали XRT двумя строками макросов; эта глава разбирает их до конца: как прямой выбор XRT_MODULE_* разворачивается через замыкание зависимостей в точное множество возможностей, как XRT_EXCLUDE_* исключает инвазивную диагностику, сохраняя полную функциональность, и как зондируются макросы компиляции XRT_FEATURE_*. Затем версии: чем ведают макросы компиляции и рантайм-функция xrtVersion() и как заголовок с библиотекой сверяют совпадение версий. Всё это обычно невидимо, но когда что-то ломается (падение линковки, странные размеры, несоответствие заголовка и библиотеки) — виноваты всегда они; этой главой том 1 закрывается, и заодно даётся полный ответ на упражнение «сравнение трёх ступеней размера» из главы 2.
8.1引入
Одна и та же библиотека должна войти в два совершенно разных хоста: инструмент командной строки в несколько сотен килобайт, только читающий JSON-конфигурацию; и резидентный фоновый сервис, которому нужны сети, TLS и весь набор HTTP. В эпоху разрозненных зависимостей это два списка зависимостей и две конфигурации сборки; к тому же «маленький инструмент» обычно вынужден линковать весь транспортный слой, потому что гранулярность дерева зависимостей останавливается на уровне «библиотеки».
Гранулярность поставки XRT — модуль: за 91 публичным заголовком стоят сотни обрезаемых модулей, каждый объявляет свои исходники и зависимости. Вы объявляете нужные корневые модули в единице трансляции, а система сборки разворачивает замыкание — инструмент линкует свои несколько тысяч строк, сервис линкует всё; одна версия, одни заголовки. Имя механизма — прямой выбор модулей: объявлять только «что нужно», никогда «что не нужно».
В сравнении с «обратным исключением» (скомпилировать всё, потом вырезать) у прямого выбора есть решающее достоинство: новые зависимости не меняют молча вашу сборку. В обратной системе какой-нибудь модуль сегодня завёл зависимость от транспортного слоя: ваш «список исключений» не изменился, а артефакт раздался. В прямой системе ваши объявленные корни не изменились, и как бы замыкание ни менялось, это затрагивает только поддерево самого корня. Отсюда предсказуемость поставки — потому книга с главы 2 учит только прямой форме записи.
8.2概念
Замыкание зависимостей: от корневых модулей к множеству возможностей
Четыре ключевых пункта. Только корни: таблицу зависимостей ведёт config/modules.json; пользователям не нужно и не следует её копировать — в этом смысл «прямого выбора». Макросы до включения: модульные макросы обязаны быть определены до первого включения xrt.h или xrt/features.h в этой единице трансляции; позже — не действует. Реализация один раз: XRT_IMPLEMENTATION определяется в одной единице трансляции проекта (корень ловушки 1 из главы 2). Согласованность множества: при модульной линковке все единицы трансляции одного экземпляра XRT должны использовать согласованное множество модулей — две единицы, выбравшие каждая своё, дают неопределённое поведение связанного экземпляра.
modules.json как единый источник истины заслуживает ещё одного взгляда: исходники, внутренние заголовки, зависимости, тесты и примеры каждого модуля живут в манифесте, а features.h (основа разворачивания замыкания) и справочные страницы сайта генерируются из него же. «Домены модулей» из главы 2, списки материалов главы 6 и поведение обрезки этой главы — всё указывает на одни данные: документация, код и сборка не говорят врозь; так «единый источник истины» воплощается в инженерии.
Макросы исключения: полная функциональность минус диагностика
Когда XRT_MODULE_ALL включает всё, а вам нужно исключить лишь инвазивную диагностику вроде отладки памяти (она меняет глобальный путь выделения — глава 6), используйте макрос исключения, а не перечисляйте вручную сотни прямых макросов: XRT_EXCLUDE_MEMORY_DEBUG исключает и memory_debug, и его модули отчётности, но сохраняет memory_stats. Макросы исключения действуют только на неявный выбор XRT_MODULE_ALL — при прямом выборе вы их и не выбирали.
Мотив конструкции стоит развернуть: диагностические модули существуют ровно в двух состояниях — «опциональное дополнение» или «то, что явно выключить» — без середины. Семантика XRT_MODULE_ALL — «дай мне полную продакшн-функциональность», а полной продакшн-функциональности как раз не нужно постоянно живущая отладочная куча памяти. Так макросы исключения стали стандартной записью «полная функциональность минус диагностика»: релизные сборки CI исключают ими отладочные модули, локальные сборки разработки сохраняют всё. Поняв этот слой, вы прочитаете, почему модульные макросы в заголовках примеров репозитория выглядят именно так — все они экземпляры этих семантик.
Зондирование на этапе компиляции: XRT_FEATURE_*
После разворачивания замыкания каждый включённый модуль определяет свой макрос семейства XRT_FEATURE_. Публичные заголовки широко используют #if defined(...) для условных объявлений — значит, и ваш код может зондировать возможности при компиляции: заголовок вправе давать разные inline-реализации в зависимости от наличия возможности TLS, не полагаясь на передачу флагов системой сборки. Заголовок атомарных операций главы 9 — один из первых потребителей.
Двойная колея версий и формы поставки
У версии две колеи (общий источник демонстрировался в главе 3): компиляции — XRT_VERSION_MAJOR/MINOR/PATCH и XRT_VERSION_TEXT (целочисленные макросы проверяются #if, строковый — для отображения), и рантайм — xrtVersion() возвращает строку версии, влинкованную в программу. При интеграции одним заголовком обе обязательно совпадают; при интеграции связываемой библиотекой могут разойтись — когда заголовок новый, а библиотека старая, xrtVersion() есть единственная правда, и сверка при старте программы до всякой работы — стандартное действие.
Поставка идёт двумя дорогами: однозаголовочник (все примеры книги; single/xrt.h самодостаточно содержит реализацию) и исходники/библиотеки (config/modules.json объявляет принадлежность; упаковке посвящён том 12). API на обеих дорогах полностью одинаков; переключение меняет только скрипт сборки. Три практических соображения выбора: скорость старта — однозаголовочник скопировал и поехал, безусловно быстрее всех; инкрементальность сборки — в средних и крупных проектах однозаголовочник означает, что цена перекомпиляции единицы реализации растёт вместе с размером библиотеки, а форма исходников/библиотек возвращает нормальную инкрементальность; границы символов — при поставке динамической библиотеки контролируется таблица экспорта, и сценарии, где несколько компонентов в процессе хоста линкуют XRT каждый сам, тоже опираются на библиотечную форму для изоляции. Обычный путь эволюции именно таков: «прототип — однозаголовочником, инженеризация — библиотекой», и ни одна строка кода при смене дороги не меняется.
8.3示例
Первая программа: минимальное множество модулей и сравнение размеров
Трёхступенчатое сравнение главы 2 получает здесь полную версию — одна программа, две ступени модулей:
/* hello.c —— обслуживает обе ступени: модульные макросы внедряются -D из командной строки */ #include <xrt.h> #include <stdio.h> int main(void) { printf("XRT %s\n", xrtVersion()); return 0; }
Что только что произошло. Модульные макросы переехали в -D командной строки — в точности эквивалентно записи в исходнике, а выгода в том, что один исходник компилируется в несколько ступеней (стандартная поза матричных тестов обрезки в CI). Ступень -DXRT_MODULE_ALL втягивает все модули (включая сети/TLS/HTTP, потому и линкуются системные сетевые библиотеки); вторая ступень не пишет ни одного модульного макроса — фундамент core присутствует всегда и не обрезаем, xrtVersion принадлежит core, поэтому достаточно определить XRT_IMPLEMENTATION, и системные сетевые библиотеки не нужны. Различие в разы показывает: «обрезка» — это не экономия на нескольких функциях, а вход и выход целых поддеревьев зависимостей.
Вторая программа: зондирование возможностей при компиляции
/* probe.c —— зондируем, какие возможности развернуло замыкание */ #define XRT_MODULE_STRING #define XRT_IMPLEMENTATION #include <xrt.h> #include <stdio.h> int main(void) { printf("version=%s\n", xrtVersion()); #if defined(XRT_FEATURE_STRING) printf("feature: string = yes\n"); #else printf("feature: string = no\n"); #endif #if defined(XRT_FEATURE_TLS) printf("feature: tls = yes\n"); #else printf("feature: tls = no\n"); #endif return 0; }
Что только что произошло. Объявлен только XRT_MODULE_STRING; после разворачивания замыкания XRT_FEATURE_STRING определён, а XRT_FEATURE_TLS нет — результаты зондирования соответствуют выбору один к одному. Ценность паттерна — в заголовках и шаблонном коде: различия возможностей разветвляются на этапе компиляции, без рантайм-расходов и без дополнительных флагов системы сборки. Учтите: макросы зондирования обязаны проверяться в той же единице трансляции, что и xrt.h (сначала включить, затем проверять), а при передаче между заголовками сохраняйте стабильный порядок включения.
Связав оба примера, получаем полную картину модульной системы: -D командной строки решает «что выбрано» (поставка), зондирование XRT_FEATURE_ отражает «что выбранось» (возможности), а xrtVersion() докладывает «кто оказывает услугу» (версия). Трое замыкаются в одной единице трансляции — вам не нужно вести никакого списка «что включено в проект» вне скрипта сборки: макросы и зонды и есть список, а компилятор сверяет согласованность за вас.
8.4契约
- Макросы до включения: модульные макросы и
XRT_IMPLEMENTATIONобязаны быть определены до первого включенияxrt.h/features.h. - Реализация ровно один раз: одна единица реализации на проект; остальные единицы включают только объявления.
- Согласованность множества: все единицы трансляции одного экземпляра XRT используют согласованное множество модулей.
- Граница макросов исключения:
XRT_EXCLUDE_*подавляет только неявный выборXRT_MODULE_ALLи не затрагивает прямые объявления. - Сверка версий: при интеграции связываемой библиотекой при старте сравните
xrtVersion()с макросами компиляции; расхождение — ошибка развёртывания.
Типичные ошибки
Симптомы: макрос явно определён, а поведение словно нет — отсутствие символов при линковке или невключённые возможности, без внятной ошибки.
Причина: разворачивание макросов происходит в точке включения; определения позже #include не действуют на уже завершённое включение.
#include <xrt.h> /* сначала включение: замыкание уже развернуто «без выбора» */ #define XRT_MODULE_STRING /* поздно — string в этой единице не включится */
#define XRT_MODULE_STRING /* объявить сначала */ #define XRT_IMPLEMENTATION #include <xrt.h>
Симптомы: причуды времени выполнения — объект ведёт себя нормально на одних путях вызова и падает на других; или отсутствие/дублирование символов при линковке.
Причина: единица A выбрала XRT_MODULE_NET_TCP, единица B только core — структуры, которые видит B при компиляции, расходятся с раскладкой A (различия условных объявлений); в одном экземпляре XRT уживились два мировоззрения.
/* a.c */ /* b.c */ #define XRT_MODULE_ALL #define XRT_MODULE_STRING #include <xrt.h> #include <xrt.h> /* две единицы с несогласованными множествами: влинкованы в один экземпляр, раскладки разошлись */
/* common.h —— единственная точка объявления модулей всего проекта */ #define XRT_MODULE_ALL /* common.c: #define XRT_IMPLEMENTATION затем include (единственная единица реализации) */ /* a.c / b.c: включают common.h напрямую; множество всегда согласовано */
Практические задания
Скомпилируйте probe.c для трёх ступеней «без модульных макросов», «XRT_MODULE_STRING», «XRT_MODULE_ALL»; запишите результат зонда tls для каждой и объясните происхождение различий одним предложением.
Скомпилируйте hello.c в пяти ступенях (без модулей / string / regex / string+regex / all); для каждой запишите, нужен ли -lws2_32, и размер бинарника, оформив таблицей. Подсказка: модульные макросы внедряются -D, выходные файлы различайте -o.
CLI-инструмент, которому нужно лишь «читать JSON-конфигурацию + писать журнал»: подберите минимальное множество модулей, проверьте компиляцию, линковку и работу, зафиксируйте итоговый размер бинарника и его отношение к ступени XRT_MODULE_ALL. Критерий приёмки: у каждого макроса множества есть обоснование в одно предложение; отношение ниже четверти; функциональная проверка выводит корректно.
Итоги
| Тема | Краткая справка |
|---|---|
| Прямой выбор | объявлять только корневые модули (семейство XRT_MODULE_); замыкание зависимостей разворачивает автоматически; поставка предсказуема |
| Порядок макросов | все макросы до первого включения xrt.h / features.h; поздние определения не действуют |
| Реализация | XRT_IMPLEMENTATION один раз на проект, в единственной единице реализации |
| Исключение | XRT_EXCLUDE_MEMORY_DEBUG и др. подавляют только неявный полный выбор XRT_MODULE_ALL |
| Зондирование | замыкание определяет семейство XRT_FEATURE_; развилка на компиляции без рантайм-расходов |
| Версия | макросы компиляции проверяются #if; при библиотечной интеграции сначала сверка xrtVersion() при старте |
| Поставка | однозаголовочник (single/) и исходники/библиотеки (modules.json) с одним API; прототип — заголовком, инженеризация — библиотекой |
| Тройка | -D выбирает поставку, XRT_FEATURE_ зондирует возможности, xrtVersion() сверяет версию — замкнуто в одной единице |
📖 Справочники API в этой главе:core (25 записей)memory_stats (5 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки