# Дональд Кнут об архитектуре TeX82, управлении памятью и строках

Источник: https://www.youtube.com/watch?v=3RDtSdN7jPI
Канал: Stanford Online
Опубликовано: 26.11.2025

---

Вторая лекция Дональда Кнута, посвященная внутреннему устройству системы компьютерной верстки TeX82, раскрывает архитектурные принципы распределения памяти и управления модулями программы. Выдающийся ученый подробно описывает, как ограничения языка Pascal привели к созданию кастомной системы обработки строк, ставшей в итоге ключевым преимуществом всей архитектуры. Лекция детально разбирает структуру компиляции, работу со строковыми пулами и гибкие механизмы вывода отладочных сообщений.

## 📊 Анатомия архитектуры TeX82 и распределение памяти
[[JUMP:0:19]]
Программа TeX82 состоит примерно из 1200 модулей. По оценкам Дональда Кнута, на один модуль приходится около 100 байт кода, что означает, что группа из 10 модулей занимает около 1 Кбайт. Архитектуру системы можно представить в виде ядра кода, окруженного большими таблицами данных.

В систему поступают три типа входных файлов:

* Исходный текст пользователя — обычный текстовый файл Pascal, состоящий из символов.
* Информация о шрифтах — TFM-файл, состоящий из 8-битных байт.
* Файл инициализации или формата — FMT-файл, который содержит внутренние структуры данных и preloaded-макросы.

Как подчеркивает Лектор, FMT-файлы являются локальными компонентами конкретной библиотеки и никогда не пересылаются между разными компьютерами.

Чтением текста и разбором макросов занимаются модули, которые Дональд Кнут метафорически называет «глазами и ртом» TeX. Программа способна обрабатывать множество уровней вложенности файлов и параметров макросов одновременно. При этом буфер ввода тривиален по размеру и занимает всего около половины килобайта. 

Синтаксическая часть отвечает за распознавание конструкций (например, перевод строки «5pt» в числовое значение), а семантический блок обрабатывает информацию дальше. В частности, математический режим строит M-списки (математические списки), в то время как обычная семантика формирует H-списки (горизонтальные) и V-списки (вертикальные).

Важную роль играют таблицы локальных переменных, называемые таблицами эквивалентностей. Там хранятся счетчики, размеры, параметры отступов (например, baseline skip) и указатели для управляющих последовательностей. Динамическая память — это место, где хранится большая часть данных TeX. 

Оптимизацией разбиения текста на строки занимается модуль сборки абзацев, который при необходимости обращается к модулю переносов. Вывод в DVI-файл реализуется относительно короткой программой, большая часть которой состоит из комментариев с описанием формата DVI.

Размер самого кода программы составляет около 260 Кбайт. Для полноценной работы требуется от 100 до 300 Кбайт динамической памяти. На некоторых 16-битных машинах существует жесткое ограничение в 256 Кбайт из-за особенностей адресации по 4 байта. 

В итоге полный объем TeX82 составляет от 350 000 до 600 000 байт плюс runtime-система Pascal. По мнению Дональда Кнута, половины мегабайта достаточно для хорошей верстки, хотя с чуть большим объемом работать комфортнее. 

Сравнивая систему с TeX80, создатель отмечает, что размер динамической памяти остался прежним, сам код стал немного меньше, но добавилась новая память для строк. При этом Лектор признает: «Я пытался подчеркнуть читаемость кода, а не его эффективность, не жертвуя при этом эффективностью слишком сильно», допуская потерю пары байт ради понятности программы.

## 🧵 Проблема строк в Pascal и «домашнее» решение Кнута
[[JUMP:12:39]]
Язык Pascal исторически славится слабой встроенной поддержкой строк. Чтобы обойти это ограничение, разработчикам пришлось создать собственный строковый механизм. Дональд Кнут упоминает, что базовую идею предложил Луис Пардо при работе над предыдущей версией TeX. 

Как считает Лектор, отсутствие встроенных строк в Pascal в итоге стало скрытым благословением: кастомное решение принесло множество неожиданных преимуществ, которые не сработали бы со стандартными строками языка.

Утилита Tangle генерирует специальный пул-файл (`tex.poo`). Строки в нем разбиты на строки максимальной длиной в 72 символа из-за ограничений системы Weave. Каждая строка пула начинается с двух символов, указывающих на ее длину. Всего файл содержит 898 строк.

Самая последняя строка файла является уникальной — это контрольная сумма строкового пула, помеченная звездочкой. Она используется для проверки синхронизации: если программист изменит хотя бы одну строку в исходном коде, контрольная сумма изменится. TeX проверяет это совпадение при загрузке. 

Если строки рассинхронизируются, TeX начнет выдавать непредсказуемые и перепутанные сообщения: например, при запросе строки номер 100 на печать выведется совершенно другой текст, что может развеселить одних пользователей, но сильно расстроить других. Для превращения пула в удобочитаемый вид используется простая служебная программа Pool Type.

## 🔤 Внутреннее кодирование и системно-зависимые кодовые таблицы
[[JUMP:18:16]]
Первые 128 строк в пуле (номера от 0 до 127) отведены под внутренние коды TeX. Символы с 32 по 126 соответствуют стандартному набору ASCII: пробел, восклицательный знак, кавычки и так далее. Невидимые или управляющие символы для обеспечения кроссплатформенности отображаются в виде двух стрелок вверх и буквы (например, код 127 выводится как `^^?`, а код 19 — как `^^S`). Если пользователь введет `^^S` в исходном тексте, TeX распознает его именно как внутренний код 19.

Дональд Кнут делится забавной историей из жизни Стэнфордского университета, где использовался особый набор символов SAIL. На клавиатурах SAIL были физические клавиши для стрелок, греческих букв и знаков неравенства. Как рассказывает Лектор, однажды ночью один из инженеров решил сэкономить пять резисторов в аппаратной части машины и самовольно поменял местами код правой фигурной скобки, никому об этом не сказав. С тех пор в Стэнфорде код правой скобки отличался от общемирового.

Чтобы локальные особенности кодировок не ломали переносимость файлов, внутренний кодировочный набор TeX был стандартизирован на основе соглашений, принятых в Массачусетском технологическом институте (MIT). Вся внешняя текстовая информация (даже если это EBCDIC) при вводе сразу преобразуется во внутренний 7-битный код TeX. Это гарантирует, что цифры от 0 до 9 всегда идут последовательно. 

Внутренние коды используются и при генерации DVI-файлов: если символ 'A' имеет внутренний код 65, именно он запишется в DVI. Если у конкретного производителя оборудования этот символ находится на другой позиции шрифта, конвертацию выполняет выходной драйвер. В самом языке TeX для перевода символа во внутренний код используется оператор обратного апострофа (например, `` `A `` возвращает 65). 

Системно-зависимые изменения реализуются через модуль `character k can be printed` (модуль 49), который определяет, какие символы считаются печатными. В стандартной версии TeX символы с кодами меньше 32 просто заменяются пробелами, тогда как в MIT они выводятся как есть.

## 🗄️ Структура данных string_pool и отказ от сборки мусора
[[JUMP:33:51]]
Внутри TeX строки представлены в виде большого массива `string_pool`, объявленного в модуле 39 как упакованный массив кодов ASCII (диапазон от 0 до 127). В этот массив попадают только видимые 7-битные печатные символы. Индекс для доступа к массиву называется `pool_pointer`.

Для навигации используется массив указателей `string_start`. Уникальность реализации заключается в том, что в TeX нет массива для хранения конечных позиций строк. Чтобы узнать, где заканчивается строка с номером *k*, программа просто смотрит на начало строки *k+1*. Соответственно, для *n* строк требуется хранить *n+1* позицию в `string_start`. Длина строки вычисляется как разность между двумя соседними указателями начала. Модуль 44 содержит процедуру сброса (flush), которая уничтожает только самую последнюю созданную строку, так как строки в TeX всегда добавляются строго последовательно в конец массива.

Макрос `string_room` проверяет наличие свободного места в пуле. Размер пула (`pool_size`) — это параметр времени компиляции, установленный Кнутом в значение 30 000. На 36-битных компьютерах Стэнфорда это занимало 6000 слов (по 5 символов в слове), на байтовых машинах — 30 Кбайт, а на 32-битных архитектурах — по 4 символа в слове. Значение `pool_size` должно как минимум на 22 000 превышать параметр `string_vacancies` (свободные места для строк, заданный в 8 000). Сам TeX «из коробки» использует 21 175 символов под сообщения об ошибках, подсказки и системные тексты.

Использование строкового пула для имен управляющих последовательностей решило множество проблем. По воспоминаниям создателя, в оригинальном проекте 1977 года действовали жесткие ограничения: имя макроса не могло превышать пяти букв, а регистр символов после первой буквы не учитывался. Переход на строки позволил убрать эти лимиты и использовать стандартные подпрограммы печати строк для вывода имен макросов. 

При этом при переопределении макросов сборка мусора (garbage collection) в пуле строк не производится. По утверждению Дональда Кнута, затраты памяти на размещение кода полноценного сборщика мусора превысили бы ту выгоду, которую он мог бы принести в реальных сценариях работы программы.

## 🖨️ Гибкая система печати и отладочные трюки Кнута
[[JUMP:47:11]]
Базовые процедуры печати (начиная со страницы 22, модуль 56) построены на концепции изменяемого назначения (destination). Программа использует единые функции (например, `print_integer`), но в зависимости от установленного селектора вывод может перенаправляться в разные места:

* На terminal пользователя (`term_only`).
* В файл транскрипта ошибок или лог-файл.
* Одновременно на терминал и в лог-файл.
* В специальный файл отправки (send file).
* Напрямую в строковый пул для динамического создания новой строки (`new_string`).
* В режим полного подавления вывода (`no_print`), когда символы просто исчезают.

Особый интерес представляет режим псевдопечати (`pseudo`, номер селектора 20). Он используется для вывода контекста ошибки в виде двух строк: первая показывает уже прочитанные символы, вторая — еще не увиденные системой. Длина раскрытого макроса может достигать 100 000 байт. 

Чтобы не выделять огромные буферы под весь текст макроса ради показа короткого фрагмента, TeX использует кольцевой буфер размером чуть больше 60 символов. Программа циклически записывает туда символы псевдопечати и останавливает запись, как только пройден критический момент ошибки, экономя память.

В процессе отладки Дональд Кнут изменил тип параметра `s` главной процедуры `print` с `str_number` на `integer`. Лектор объясняет это стремлением к максимальной отказоустойчивости отладочных подпрограмм. Если память компьютера окажется полностью разрушена («замусорена») из-за бага, процедура `print` все равно должна попытаться прочитать указатель и вывести данные, не вызывая аварийного завершения самой среды Pascal. 

Внутри процедуры стоит проверка: если номер строки выходит за границы пула, он принудительно сбрасывается в 0. При этом отладочная печать принципиально не имеет права генерировать сообщения об ошибках, иначе возникла бы бесконечная рекурсия: ошибка вызывает печать, а печать — ошибку. Строка номер 0 выводит странный символ `^^@`, что само по себе служит сигналом о сбое. 

Нижний уровень печати обеспечивается функцией `print_char`, которая извлекает символ из буфера и через системно-зависимый массив `xchr` отправляет его на терминал или в файл.