Таблица символов

Таблица символов — это структура данных, используемая трансляторами языков программирования, такими как компиляторы и интерпретаторы, для хранения информации о каждом идентификаторе, символе, константе, процедуре и функции, встречающихся в исходном коде программы. Для каждого элемента исходного кода таблица символов связывает сведения, относящиеся к его объявлению или использованию[1].

Предпосылки

Таблица символов может существовать только в оперативной памяти во время процесса трансляции, либо быть встроенной в выходные данные трансляции, например, в объектный файл ABI — для последующего использования. Например, таблица символов может использоваться во время отладки или как источник сведений при формировании диагностического отчёта во время или после выполнения программы[2].

Описание

Минимальная информация, содержащаяся в таблице символов, используемой транслятором и промежуточным представлением, включает в себя имя символа и его местоположение или адрес. Для компиляторов, целевыми платформами которых являются системы с поддержкой перемещаемости кода, таблица также содержит соответствующие атрибуты (абсолютный, релокейтабельный(перемещаемый) и т. д.) и необходимые сведения для обработки таких символов. В таблицах символов для языков высокого уровня могут храниться сведения о типе (строка, целое число, число с плавающей запятой и др.), размере, измерениях и границах символа. Не вся эта информация включается в выходной файл, но может предоставляться для целей отладки. Во многих случаях к каждому символу хранится перекрёстная ссылка. Большинство компиляторов формируют итоговые отчёты, где перечисляют информацию из таблицы символов и перекрестных ссылок[1].

Реализация

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

Компилятор может использовать единую большую таблицу символов для всех символов или раздельные (иерархические) таблицы для каждого блока видимости. Например, в языках с блочной структурой, таких как ALGOL или PL/I, символ «p» может быть объявлен неоднократно — в разных процедурах и с разными атрибутами. Область видимости каждого объявления — это участок программы, где использование «p» разрешается к этой конкретной декларации. Таблица символов должна обеспечивать различие между такими случаями.

Часто таблицы символов реализуются в виде хеш-таблиц. Время поиска в хеш-таблице не зависит от количества хранимых элементов, что делает этот подход эффективным при большом их числе. Кроме того, это упрощает классификацию литералов — необходимая информация включается в вычисление хеш-ключа[3].

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

Применение

Объектный файл содержит таблицу символов используемых идентификаторов, видимых извне. При компоновке нескольких объектных файлов компоновщик идентифицирует и разрешает такие символы. Обычно все неопределённые внешние символы ищутся в одной или нескольких библиотеках объектных файлов. Если найден модуль, определяющий требуемый символ, он связывается с первым объектным файлом, а все новые неопределённые идентификаторы добавляются к списку для поиска. Этот процесс продолжается до тех пор, пока все внешние ссылки не будут разрешены. Если после завершения процесса остались неразрешённые ссылки, это считается ошибкой.

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

Пример

Рассмотрим следующий фрагмент программы на языке C:

// Объявление внешней функции
extern double bar(double x);

// Определение публичной функции
double foo(int count)
{
    double sum = 0.0;

    // Суммирование значений bar(1) до bar(count)
    for (int i = 1; i <= count; i++)
        sum += bar((double) i);
    return sum;
}

Компилятор C, который анализирует этот код, как правило, сформирует как минимум следующие записи таблицы символов:

Имя символа Тип Область видимости
bar функция, double extern
x double параметр функции
foo функция, double глобальная
count int параметр функции
sum double локальная (блочная)
i int оператор цикла for

Кроме перечисленных, компилятор может добавить в таблицу служебные записи, генерируемые для промежуточных выражений (например, преобразование i в double), возвратных значений функций, меток перехода и т. п.

SysV ABI

Пример таблицы: SysV ABI
Адрес Тип Имя
00000020 a T_BIT
00000040 a F_BIT
00000080 a I_BIT
20000004 t irqvec
20000008 t fiqvec
2000000c t InitReset
20000018 T _main
20000024 t End
20000030 T AT91F_US3_CfgPIO_useB
2000005c t AT91F_PIO_CfgPeriph
200000b0 T main
20000120 T AT91F_DBGU_Printk
20000190 t AT91F_US_TxReady
200001c0 t AT91F_US_PutChar
200001f8 T AT91F_SpuriousHandler
20000214 T AT91F_DataAbort
20000230 T AT91F_FetchAbort
2000024c T AT91F_Undef
20000268 T AT91F_UndefHandler
20000284 T AT91F_LowLevelInit
200002e0 t AT91F_DBGU_CfgPIO
2000030c t AT91F_PIO_CfgPeriph
20000360 t AT91F_US_Configure
200003dc t AT91F_US_SetBaudrate
2000041c t AT91F_US_Baudrate
200004ec t AT91F_US_SetTimeguard
2000051c t AT91F_PDC_Open
2000059c t AT91F_PDC_DisableRx
200005c8 t AT91F_PDC_DisableTx
200005f4 t AT91F_PDC_SetNextTx
20000638 t AT91F_PDC_SetNextRx
2000067c t AT91F_PDC_SetTx
200006c0 t AT91F_PDC_SetRx
20000704 t AT91F_PDC_EnableRx
20000730 t AT91F_PDC_EnableTx
2000075c t AT91F_US_EnableTx
20000788 T __aeabi_uidiv
20000788 T __udivsi3
20000884 T __aeabi_uidivmod
2000089c T __aeabi_idiv0
2000089c T __aeabi_ldiv0
2000089c T __div0
200009a0 D _data
200009a0 A _etext
200009a4 A __bss_end__
200009a4 A __bss_start
200009a4 A __bss_start__
200009a4 A _edata
200009a4 A _end

Пример таблицы символов можно найти в спецификации SysV ABI (англ. System V Application Binary Interface), где стандартизируется способ размещения символов в бинарных файлах. Это обеспечивает совместную работу различных компиляторов, компоновщиков и загрузчиков с символами в скомпилированных объектах.

SysV ABI реализован, например, в утилите англ. nm пакета GNU binutils. Формат таблицы включает отсортированное поле с адресом памяти, поле «тип символа» и идентификатор символа («имя»)[4].

Типы символов в спецификации SysV ABI (и выводе утилиты англ. nm) характеризуют назначение каждой записи: например, «d» для инициализированных данных, а «t» для функций (исполняемый код размещается в секции «text» объектного файла). Заглавные буквы обозначают внешнюю (глобальную) связь, а строчные — локальную.

Таблица символов Python

Язык программирования Python содержит широкие возможности для создания и анализа таблиц символов[5]. Можно, например, проверять, является ли символ свободной или связанной переменной, к какому пространству имён относится, импортирован ли он и принадлежит ли к блочной или глобальной области видимости.

Динамические таблицы символов

В некоторых языках таблица символов может изменяться во время выполнения программы — позволяется создавать новые символы на лету. Примером такого языка является англ. Racket[6].

Языки LISP и Scheme позволяют ассоциировать с каждым символом произвольные свойства[7].

Язык Prolog во многом представляет собой язык обработки таблицы символов: символы называются атомами, а взаимосвязи между ними могут анализироваться логически. Похожая система реализована в OpenCog, где динамическая таблица символов («atomspace») служит для представления знаний.

Примечания

  1. 1 2 Copper, Keith D. Engineering a Compiler : [англ.] / Keith D. Copper, Linda Torczon. — 2. — Хьюстон, Техас : Elsevier, Rice University, 18 января 2011. — P. 253. — ISBN 978-0-12-088478-0. — doi:10.1016/C2009-0-27982-7.
  2. Nguyen, Binh. Linux Dictionary : [англ.]. — 2004. — P. 1482.
  3. Copper, Keith D. Engineering a Compiler : [англ.] / Keith D. Copper, Linda Torczon. — 2. — Хьюстон, Техас : Elsevier, Rice University, 18 января 2011. — P. 254. — ISBN 978-0-12-088478-0. — doi:10.1016/C2009-0-27982-7.
  4. nm (англ.). sourceware.org. Дата обращения: 18 июня 2024. Архивировано 8 сентября 2025 года.
  5. Python documentation: symtable (англ.). python.org. Дата обращения: 18 июня 2024. Архивировано 1 ноября 2012 года.
  6. Racket Documentation: Symbols (англ.). racket-lang.org. Дата обращения: 18 июня 2024. Архивировано 3 июля 2010 года.
  7. Guile Documentation: Symbols (англ.). gnu.org. Дата обращения: 18 июня 2024. Архивировано 20 сентября 2025 года.

Литература

Категории