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

Глава 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概念

Позитивный выбор и замыкание

1Объявление#define XRT_MODULE_STRING_SPLIT (корневой модуль) -> #include "xrt.h"
2Разворотfeatures.h разворачивает замыкание по манифесту - базовый string + split приезжают сами
3Единица реализацииXRT_IMPLEMENTATION определяет ровно одна трансляция
4Согласованностьпри модульной линковке все TU используют одинаковый набор модулей

Дисциплина тайминга: макросы выбора обязаны быть определены до первого 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 образуют свои замыкания. Новая публичная возможность сначала через три вопроса: можно ли тестировать независимо? обрезать независимо? описать владение независимо? — всё да, тогда новый модуль; существующий общий макрос не расширять (механизм, не дающий гранулярности деградировать).

Профили размера и запрет символов

1Профилькаждая запись size_profiles.json - {name, suite, forbid_symbols}
2Измерениеmeasure_size.py компилирует по профилю -> записывает базовую линию среды (платформа/компилятор/оптимизация/stripped)
3Шлюзпоявление любого запрещённого символа - провал; ошибочно притянутая зависимость вскрывается на месте

Смысл базовой линии среды: размеры бинарников несравнимы между компиляторами/уровнями оптимизации/платформами — отчёт записывает семь элементов platform/machine/compiler_family/compiler_version/arch/optimization/stripped, сравнение трендов идёт только внутри одной линии (пролог метода измерения главы 136).

131.3示例

Первая полная программа: минимальная интеграция Core

Программа ниже из examples/core/version_limits — работа под свободным от возможностей контрактом Core:

examples/core/version_limits/main.c源码 ↗
















 */

#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;
}
$ gcc -O1 -I single -include xrt.h impl.c examples/core/version_limits/main.c -lws2_32 -liphlpapiversion=2.0.0-devlimits: depth=128 entries=100000 input=268435456

Что только что произошло. (1) В строке компиляции нет XRT_MODULE_ALL — без выбора = только Core: запрос версии и ресурсные границы — часть контракта Core (структурированные ошибки/слот ошибки туда же), а построение ошибки с printf — отдельный выбор XRT_MODULE_ERROR_FORMAT — минимальная программа не везёт форматный рантайм (пример из самого контракта). (2) Сравните со строкой главы 130 (с ALL): один пример — две формы интеграции — ALL — беспечная форма учебника, без выбора — фактическое заявление минимальной интеграции. (3) Угол размера: артефакт этой формы и есть профиль размера core — запрет символов профиля утверждает, что в нём нет ни одного символа regex/сжатия/сети.

Вторая полная программа: минимальная интеграция с заменой распределителя

Вторая программа из examples/core/allocator_tour — подключение хозяина внутри замыкания Core:

examples/core/allocator_tour/main.c源码 ↗



















 */

#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;
}
$ gcc -O1 -I single -include xrt.h impl.c examples/core/allocator_tour/main.c -lws2_32 -liphlpapiallocator: swap -> at-family alloc/calloc/dup/realloc okallocator: locked after first alloc -> swap rejected ok

Что только что произошло. (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 для хозяев/инструментов.

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

Ловушка 1: define макросов модуля после include:Ловушка 1: define макросов модуля после include

Симптом: 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"
Ловушка 2: копирование таблицы зависимостей (ручной список всех макросов):Ловушка 2: копирование таблицы зависимостей (ручной список всех макросов)

Симптом: после обновления библиотеки сборка ломается — внутренние зависимости изменились, ваша копия устарела.

Причина: замыкание зависимостей — частное дело библиотеки — контракт прямо говорит «таблицу зависимостей копировать не следует». Объявляйте только нужный корень (STRING_SPLIT, а не STRING + внутренняя комбинация STRING_SPLIT).

错误写法
/* «замыкание», переписанное руками */
#define XRT_MODULE_STRING
#define MY_SPLIT_LEGACY_PATCH /* ручная заплата, копирующая старую зависимость */
正确写法
#define XRT_MODULE_STRING_SPLIT  /* объявить только корень - замыкание развернётся внутри библиотеки */
Ловушка 3: сравнение размеров между средами:Ловушка 3: сравнение размеров между средами

Симптом: «это изменение раздуло бинарник на 30%» — сверились с базой: в прошлый раз gcc, в этот tcc.

Причина: размеры несравнимы между компиляторами/уровнями оптимизации/strip; семиэлементная базовая линия measure_size существует ровно против этого. Сравнение трендов — только в одной линии (тот же профиль, та же среда), или вместо абсолютных размеров — запрет символов.

正确写法
/* сверить семь элементов базовой линии отчёта и только потом сравнивать; шлюз размера - forbid_symbols (от среды не зависит) */

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

Базовое: сравнение трёх форм интеграции

Один и тот же пример собрать без выбора / MODULE_STRING_SPLIT / MODULE_ALL — сравнить таблицы символов (nm/findstr) и размеры. Критерии приёмки: наборы символов трёх форм строго растут; в форме без выбора ноль не-Core символов.

Продвинутое: проверка семантики EXCLUDE

Собрать с 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 записей)

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