Ориентация главы
Глава 130 показала, как сборка компилирует комбинации обрезки; эта глава — о том, как обрезка объявляется и как проверяется, что размер действительно уменьшился. Три блока: модель позитивного выбора (XRT_MODULE_* объявляет только корневые модули, замыкание зависимостей разворачивается автоматически — «пользователю не нужно знать, от каких внутренних возможностей модуль зависит сейчас, и не следует копировать таблицу зависимостей»); семантика исключения ALL+EXCLUDE (XRT_MODULE_ALL+XRT_EXCLUDE_MEMORY_DEBUG — точное выражение «всё минус диагностика»; явные корневые модули приоритетнее исключений); принцип гранулярности (один публичный модуль = одна небольшая группа возможностей с общим состоянием/зависимостями/сценарием — без макросов на функцию и без общего рубильника; новая возможность сначала отвечает на три вопроса «можно ли независимо тестировать/обрезать/описать владение»). Проверка размера: профили config/size_profiles.json (каждый профиль — suite + таблица запрета forbid_symbols) с tools/measure_size.py — профиль core запрещает любые символы семейств регулярных выражений/сжатия/сети/протоколов — обрезка — это не заявление, а проверенный факт.
131.1引入
Встраиваемая цель не вмещает всю библиотеку? Отрежьте ненужное — звучит просто, но у исторических реализаций три болезни: рукописные таблицы зависимостей (пользователь сам перечисляет все нужные макросы — внутренняя зависимость меняется, и у всех пользователей копии ломаются); неявная полнота (без выбора даём всё — «минимальная программа» тайно везёт TLS); ложная гранулярность (макросы на функцию — взрыв, которым никто не пользуется; или один общий макрос — обрезка превращается во всё-или-ничего). Три лекарства XRT: разворачивание замыкания (объявляем корень, разворачиваем зависимости — таблица ведётся в одном месте, внутри библиотеки); без выбора = только Core («когда не определён ни один XRT_MODULE_*, предоставляется только свободный от возможностей контракт Core» — минимальная интеграция предсказуема, новые модули незаметно не увеличивают существующие программы); три вопроса гранулярности (независимое тестирование/независимая обрезка/независимо описываемое владение — всё три выполнены, только тогда новый модуль).
Дизайн forbid_symbols заслуживает акцента: профиль размера — не просто «собрать этот suite», он ещё утверждает, что в артефакте нет чужих символов: если в линковке профиля core всплывает символ семейства TLS (зависимость втащена по ошибке), measure падает сразу. Ошибки обрезки вскрываются на шлюзе, а не когда бинарник в проде уже располнел.
131.2概念
Позитивный выбор и замыкание
Дисциплина тайминга: макросы выбора обязаны быть определены до первого include xrt.h/features.h — define после include недействителен (развёртывание уже произошло).
Семантика ALL и EXCLUDE
XRT_MODULE_ALL: для хозяев/компиляторов/отладочных инструментов, которым нужна полная среда исполнения. XRT_EXCLUDE_MEMORY_DEBUG: ALL минус отладка памяти (исключаются сразу memory_debug и memory_debug_report, остаётся memory_stats) — точное выражение «полная продакшн-функциональность без инвазивной диагностики». Правило приоритета: EXCLUDE подавляет только неявный выбор ALL; явно определённые корневые модули всё равно побеждают (ALL+EXCLUDE_X+явный MODULE_X = X входит). Совет по умолчанию (буквально из контракта): «обычным приложениям не следует по умолчанию выбирать все модули».
Семантику комбинации ALL+EXCLUDE запомните одним выводом: XRT_MODULE_ALL неявно выбирает все модули манифеста; XRT_EXCLUDE_MEMORY_DEBUG убирает из неявного множества два диагностических модуля — в этот момент артефакт = всё минус диагностика; затем явный #define XRT_MODULE_MEMORY_DEBUG — явный корневой модуль добавляется обратно, перекрывая исключение (порядок трёх слоёв: явное > неявное ALL > исключение EXCLUDE). Этот приоритет позволяет сосуществовать «всё минус одно» и «точечно по имени» — хозяин подстраивает модуль за модулем поверх ALL, не теряя удобства умолчания.
Принцип гранулярности: три вопроса о модуле
Один публичный модуль = одна небольшая группа возможностей с общим состоянием, общими зависимостями, общим сценарием. Антипримеры: макрос на функцию (взрыв макросов)/один рубильник на семейство (всё-или-ничего). Семейство Queue и сетевое семейство — положительные образцы: база Queue/SPSC/MPSC/MPMC/ожидание/отмена/Select/мост к корутинам выбираются по отдельности; Engine сети/каждый бэкенд/TCP/UDP/синхронизация/Future/DNS/прокси/TLS образуют свои замыкания. Новая публичная возможность сначала через три вопроса: можно ли тестировать независимо? обрезать независимо? описать владение независимо? — всё да, тогда новый модуль; существующий общий макрос не расширять (механизм, не дающий гранулярности деградировать).
Профили размера и запрет символов
Смысл базовой линии среды: размеры бинарников несравнимы между компиляторами/уровнями оптимизации/платформами — отчёт записывает семь элементов platform/machine/compiler_family/compiler_version/arch/optimization/stripped, сравнение трендов идёт только внутри одной линии (пролог метода измерения главы 136).
131.3示例
Первая полная программа: минимальная интеграция Core
Программа ниже из examples/core/version_limits — работа под свободным от возможностей контрактом Core:
*/ #include <stdio.h> #include <xrt.h> int main(void) { xrtresourcelimits Limits; printf("version=%s\n", xrtVersion()); xrtResourceLimitsInit(&Limits); printf("limits: depth=%u entries=%llu input=%llu\n", (unsigned)Limits.iMaxDepth, (unsigned long long)Limits.iMaxEntries, (unsigned long long)Limits.iMaxInputBytes); return 0; }
Что только что произошло. (1) В строке компиляции нет XRT_MODULE_ALL — без выбора = только Core: запрос версии и ресурсные границы — часть контракта Core (структурированные ошибки/слот ошибки туда же), а построение ошибки с printf — отдельный выбор XRT_MODULE_ERROR_FORMAT — минимальная программа не везёт форматный рантайм (пример из самого контракта). (2) Сравните со строкой главы 130 (с ALL): один пример — две формы интеграции — ALL — беспечная форма учебника, без выбора — фактическое заявление минимальной интеграции. (3) Угол размера: артефакт этой формы и есть профиль размера core — запрет символов профиля утверждает, что в нём нет ни одного символа regex/сжатия/сети.
Вторая полная программа: минимальная интеграция с заменой распределителя
Вторая программа из examples/core/allocator_tour — подключение хозяина внутри замыкания Core:
*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <xrt.h> /* 计数分配器:仍路由系统堆,便于验证替换生效。 */ typedef struct examplecount { size_t iAllocs; size_t iFrees; } examplecount; static ptr exampleAlloc(ptr pContext, size_t iSize) { ++((examplecount*)pContext)->iAllocs; return (ptr)malloc(iSize); } static ptr exampleRealloc(ptr pContext, ptr pMemory, size_t iSize) { (void)pContext; return (ptr)realloc(pMemory, iSize); } static void exampleFree(ptr pContext, ptr pMemory) { ++((examplecount*)pContext)->iFrees; free(pMemory); } int main(void) { xallocator Default; xallocator Custom; examplecount Count; uint8* pBlock; uint8* pDup; int iResult = 1; /* ---- 必须在任何 XRT 分配前完成替换。 ---- */ xrtGetAllocator(&Default); memset(&Count, 0, sizeof(Count)); Custom.Context = (ptr)&Count; Custom.Alloc = exampleAlloc; Custom.Realloc = exampleRealloc; Custom.Free = exampleFree; if ( !xrtSetAllocator(&Custom) ) { goto Cleanup; } /* ---- At 族五接口:位置参数不影响堆行为。 ---- */ pBlock = (uint8*)xrtMallocAt(4u, __FILE__, __LINE__); if ( (pBlock == NULL) || ((pDup = (uint8*)xrtMemDupAt("hi", 2u, __FILE__, __LINE__)) == NULL) || (memcmp(pDup, "hi", 2u) != 0) || ((pBlock = (uint8*)xrtReallocAt(pBlock, 16u, __FILE__, __LINE__)) == NULL) || (xrtCallocAt(2u, 4u, __FILE__, __LINE__) == NULL) ) { goto Cleanup; } /* Calloc 清零验证单独做——保留指针用于释放。 */ { uint8* pZero = (uint8*)xrtCallocAt(2u, 4u, __FILE__, __LINE__); if ( (pZero == NULL) || (pZero[0] != 0u) || (pZero[7] != 0u) ) { goto Cleanup; } xrtFreeAt(pZero, __FILE__, __LINE__); } xrtFreeAt(pDup, __FILE__, __LINE__); xrtFreeAt(pBlock, __FILE__, __LINE__); printf("allocator: swap -> at-family alloc/calloc/dup/realloc ok\n"); * 且 GetAllocator 仍报告已安装的计数分配器。 ---- */ * 分配才经过自定义 Alloc;计数只需 >= 1 即证明替换生效。 */ if ( xrtSetAllocator(&Default) || (Count.iAllocs < 1u) ) { goto Cleanup; } { xallocator Current; xrtGetAllocator(&Current); if ( (Current.Alloc != Custom.Alloc) || (Current.Free != Custom.Free) ) { goto Cleanup; } } printf("allocator: locked after first alloc -> swap rejected ok\n"); iResult = 0; Cleanup: return iResult; }
Что только что произошло. (1) Та же минимальная форма без ALL — xrtSetAllocator часть грани памяти Core (глава 5). (2) Обрезочный смысл блокировки после первого распределения: встраивающий хозяин (глава 135) меняет распределитель на входе в main — дальше все распределения библиотеки идут хозяину; блокировка гарантирует, что в сценариях многих модулей/многих TU его уже не сменить (стабильность интеграции). (3) Обрезка + замена распределителя — стандартная поза встраиваемой интеграции: малый размер + память под управлением хозяина — два примера вместе дают полный портрет минимальной интеграции.
Против обрезочных взглядов хозяев-языков (проектное наблюдение)
Проблема обрезки существует и в мире хозяев-языков (инстанциация шаблонов C++, feature-гейтинг Rust); сопоставление проясняет выбор. Rust features: Cargo.toml объявляет фичи + замыкания зависимостей — модель почти изоморфна (позитивный выбор + разворот замыкания); отличие в том, что assert на forbid_symbols у XRT (проверка на уровне артефакта) строже большинства экосистем. Шаблоны C++: «обрезка» происходит автоматически по инстанциации — нулевая стоимость объявления, но результат непредсказуем (расползание инстанциаций) и непроверяем (эквивалента «запрещённых символов» нет). XRT выбирает явное объявление + утверждение артефакта: лишняя строка макроса покупает «что отрезано» как проверяемый факт — во встраиваемой поставке эта определённость дороже удобства.
131.4契约
- Позитивный выбор: XRT_MODULE_* объявляет корневые модули; замыкание разворачивается по манифесту; пользователь не копирует таблицу зависимостей.
- Тайминг: макросы выбора до первого include; XRT_IMPLEMENTATION ровно в одной TU; набор TU на линковке согласован.
- Семантика без выбора: только свободный от возможностей контракт Core — новые модули незаметно не увеличивают существующие программы.
- Семантика EXCLUDE: подавляет неявный выбор ALL; явные корни побеждают; исключение MEMORY_DEBUG забирает report, оставляет stats.
- Три вопроса гранулярности: независимое тестирование/независимая обрезка/независимо описываемое владение — всё три, только тогда модуль.
- Образцы гранулярности: восемь частей Queue и сетевое семейство по пунктам — без макросов на функцию и без рубильника.
- Профили размера: {name, suite, forbid_symbols}; появление запрещённого символа — провал шлюза.
- Базовая линия среды: семь элементов (платформа/машина/семейство компилятора/версия/архитектура/оптимизация/strip) — тренды сравниваются только внутри одной линии.
- Совет: обычным приложениям не ALL по умолчанию — ALL для хозяев/инструментов.
Типичные ошибки
Симптом: API модуля невидимы (компилятор сообщает «не объявлено») — хотя define точно был.
Причина: развёртывание происходит при первом include; последующие define не действуют задним числом. Макросы обязаны стоять до всякого include xrt.h/features.h (ключ -D в командной строке или самая верхушка файла).
#include "xrt.h" /* include вперёд: развёртывание Core уже зафиксировано */ #define XRT_MODULE_QUEUE /* поздний выбор: недействителен */
#define XRT_MODULE_QUEUE /* объявление наверху */ #define XRT_IMPLEMENTATION #include "xrt.h"
Симптом: после обновления библиотеки сборка ломается — внутренние зависимости изменились, ваша копия устарела.
Причина: замыкание зависимостей — частное дело библиотеки — контракт прямо говорит «таблицу зависимостей копировать не следует». Объявляйте только нужный корень (STRING_SPLIT, а не STRING + внутренняя комбинация STRING_SPLIT).
/* «замыкание», переписанное руками */ #define XRT_MODULE_STRING #define MY_SPLIT_LEGACY_PATCH /* ручная заплата, копирующая старую зависимость */
#define XRT_MODULE_STRING_SPLIT /* объявить только корень - замыкание развернётся внутри библиотеки */Симптом: «это изменение раздуло бинарник на 30%» — сверились с базой: в прошлый раз gcc, в этот tcc.
Причина: размеры несравнимы между компиляторами/уровнями оптимизации/strip; семиэлементная базовая линия measure_size существует ровно против этого. Сравнение трендов — только в одной линии (тот же профиль, та же среда), или вместо абсолютных размеров — запрет символов.
/* сверить семь элементов базовой линии отчёта и только потом сравнивать; шлюз размера - forbid_symbols (от среды не зависит) */Практические задания
Один и тот же пример собрать без выбора / MODULE_STRING_SPLIT / MODULE_ALL — сравнить таблицы символов (nm/findstr) и размеры. Критерии приёмки: наборы символов трёх форм строго растут; в форме без выбора ноль не-Core символов.
Собрать с MODULE_ALL+XRT_EXCLUDE_MEMORY_DEBUG — подтвердить, что API отладки памяти отсутствуют, а stats на месте; затем добавить явный XRT_MODULE_MEMORY_DEBUG — подтвердить приоритет явного (отладочные API возвращаются). Критерии приёмки: все три проверки символов сходятся с контрактом.
Написать новый профиль для цели «только контейнеры + строки»: suite фиксирует корневые модули, forbid_symbols перечисляет символы сетевых/протокольных семейств — прогнать measure_size. Затем намеренно втащить неверную зависимость в suite — наблюдать провал запрета. Критерии приёмки: чистый профиль весь зелёный; профиль с ошибкой проваливается на запрещённом символе (ошибка называет символ по имени).
Итоги
| Тема | Кратко |
|---|---|
| Модель выбора | позитивные корневые модули + разворот замыкания — таблицу зависимостей не копировать |
| Тайминг | макросы до первого include; IMPLEMENTATION ровно в одной TU |
| Без выбора | только контракт Core — минимальная интеграция предсказуема |
| EXCLUDE | подавляет неявное ALL; явные корни побеждают; MD-исключение забирает report, оставляет stats |
| Три вопроса гранулярности | независимые тестирование/обрезка/владение — всё три, только тогда модуль |
| Образцы гранулярности | восемь частей Queue, сетевое семейство по пунктам — без макросов на функцию, без рубильника |
| Профили размера | name+suite+forbid_symbols — нулевое появление запрещённого |
| Базовая линия среды | семь элементов — тренды сравниваются только в одной линии |
| Совет | обычным приложениям не ALL — ALL принадлежит хозяевам/инструментам |
📖 Справочники API в этой главе:core (25 записей)error (34 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки