Тэгированный указатель
Тэгированный указатель — это тип данных, представляющий собой адрес памяти, дополненный связанной с ним информацией, такой как метки управления памятью или счётчики ссылок. Дополнительные данные, называемые тэгами, обычно встраиваются непосредственно в представление указателя, используя особенности адресного пространства. Термин происходит из систем с тэговой архитектурой, где часть битов каждого слова памяти аппаратно зарезервирована для указания его назначения. Хотя строго «тэг» означает данные, определяющие тип, в широком смысле термин «тэгированный указатель» применяется к любым дополнительным данным, ассоциированным с адресом.
- Встраивание тэгов в указатель
Существует несколько методов включения тэгов в указатели. Во многих архитектурах наименьшим адресуемым элементом является байт, однако данные часто выравниваются по границам машинного слова или кратных им размеров. Это создаёт неиспользуемые младшие биты в адресе, которые можно задействовать под тэги, обычно в виде битовых полей. При этом код, работающий с указателем, должен применять битовые маски перед обращением к памяти. Например, в 32-битных системах слово занимает 4 байта, поэтому выровненные адреса кратны 4, освобождая два младших бита. В 64-битных архитектурах слово составляет 8 байт, что даёт три свободных бита. Если выравнивание кратно размеру слова, доступно ещё больше битов. В системах с байтовой адресацией, но без выравнивания, свободные биты отсутствуют.
В некоторых операционных системах виртуальные адреса имеют меньшую разрядность, чем шина данных, что оставляет старшие биты для тэгов. Этот подход можно комбинировать с использованием младших битов от выравнивания. Особенно актуально это для 64-битных архитектур, где 64 бита избыточны для большинства приложений. Многие современные 64-битные процессоры работают с адресами уменьшенной ширины. Стоит отметить, что разрядность виртуального адреса может быть меньше физического, который, в свою очередь, может быть уже архитектурного. Некоторые процессоры, например x86-64, запрещают тегированные указатели, требуя канонической формы адреса со старшими битами, равными 0 или 1.
Кроме того, системы виртуальной памяти в современных ОС резервируют область около адреса 0 как неиспользуемую. Это позволяет использовать нулевое значение как специальный указатель null. В отличие от предыдущих методов, здесь доступно только одно специальное значение, а не дополнительные данные для любого указателя.
- Примеры использования
Одним из первых коммерческих примеров аппаратной поддержки тэгированных указателей стала IBM System/38. Позднее IBM включила эту функциональность в архитектуру PowerPC для совместимости с ОС IBM i, наследницей System/38.
Значительный пример — среда выполнения Objective-C в iOS 7 на архитектуре AArch64 (например, в iPhone 5S). Здесь виртуальные адреса используют 33 бита, оставляя 31 бит из 64 для тэгов. Указатели на классы Objective-C выровнены по 8 байтам, освобождая ещё 3 бита. Эти битовые поля применяются для хранения счётчиков ссылок и информации о деструкторах объектов.
В ранних версиях macOS использовались тэгированные указатели на объекты данных, называемые Handles. Старшие биты указывали, заблокирован ли объект, выгружен или загружен из ресурсного файла. Это вызвало проблемы совместимости при переходе с 24-битной на 32-битную адресацию в System 7.
- Null-указатели и выровненные указатели
Использование нуля для обозначения null-указателя широко распространено, и многие языки программирования, такие как Ада, явно на это опираются. Теоретически, другие значения из зарезервированной области памяти могли бы обозначать особые состояния, но на практике это встречается редко из-за сложностей переносимости. В программной инженерии принято, что если требуется специальное значение, отличное от null, программист должен явно его определить.
Выравнивание указателей предоставляет больше гибкости, чем null-значения, позволяя маркировать указатели информацией о типе данных, условиях доступа или иными метками. Это даёт возможность сопровождать каждый допустимый указатель дополнительными данными. В тэговых архитектурах, таких как Лисп-машины, аппаратно поддерживается интерпретация и обработка тэгированных указателей.
Функция `malloc()` в glibc возвращает адреса, выровненные по 8 байтам для 32-битных платформ и по 16 байтам для 64-битных. Большее выравнивание можно получить с помощью `posix_memalign()`.
- Примеры кода
- Пример 1
В этом примере на языке C нулевое значение используется для обозначения null-указателя:
```c void optionally_return_a_value (int* optional_return_value_pointer) {
/* ... */ int value_to_return = 1;
/* Проверка на не-NULL (в C NULL, логическое false и ноль эквивалентны) */ if (optional_return_value_pointer) /* Если указатель действителен, передаём значение */ *optional_return_value_pointer = value_to_return;
/* В противном случае указатель не разыменовывается */
} ```
- Пример 2
Здесь адрес глобальной переменной используется как сторожевой указатель:
```c
- define SENTINEL &sentinel_s
node_t sentinel_s;
void do_something_to_a_node (node_t * p) {
if (NULL == p) /* Действие для null-указателя */ else if (SENTINEL == p) /* Действие для сторожевого значения */ else /* Обработка действительного указателя на узел */
} ```
- Пример 3
Предположим, структура данных `table_entry` выровнена по 16 байтам, поэтому младшие 4 бита указателей всегда нулевые. Эти биты можно использовать для хранения дополнительной информации: например, бит 0 — «только для чтения», бит 1 — «dirty» (требуется обновление). Для 16-битных указателей: - `0x3421` — указатель «только для чтения» на `table_entry` по адресу `0x3420` - `0xf472` — указатель «dirty» на `table_entry` по адресу `0xf470`
- Преимущества
Основное достоинство тэгированных указателей — экономия памяти, так как тэг хранится вместе с указателем, а не в отдельном поле. Это особенно важно, когда указатель является возвращаемым значением функции или при работе с крупными таблицами указателей.
Ещё одно преимущество — возможность атомарного обновления указателя и его тэга без дополнительных механизмов синхронизации, что повышает производительность, особенно в операционных системах.
- Недостатки
Тэгированные указатели имеют общие проблемы с такими структурами, как XOR-связные списки. Например, не все отладчики корректно работают с тэгированными указателями, хотя специализированные отладчики лишены этого недостатка.
Использование нуля для null-указателя лишено этих проблем, универсально и поддерживается большинством языков программирования. Исключение — перегрузка функций в C++, где ноль интерпретируется как целое число, а не указатель, поэтому предпочтительнее использовать `nullptr`. В системах с тэгированными указателями ноль обычно не применяется для обозначения null.
- Примечания
- Литература
Категория:Программные конструкции Категория:Указатели (программирование)