Введение

В информатике помеченный указатель — это указатель (конкретно адрес памяти) с дополнительными данными, связанными с ним, такими как бит косвенности или счётчик ссылок. Эти дополнительные данные часто "упаковываются" в указатель, то есть хранятся непосредственно в данных, представляющих адрес, используя определённые свойства адресации памяти. Название происходит от систем с "маркированной архитектурой", которые резервировали биты на аппаратном уровне для указания значимости каждого слова; дополнительные данные называются "тегом" или "тегами", хотя строго говоря "тег" относится к данным, определяющим тип, а не к другим данным; однако термин "помеченный указатель" используется повсеместно.

Складывание меток в указатель

Существуют различные методы кодирования тегов в указатель. Ненадежный источник: причина=Пример, основанный на представлении чисел с тегами, является фактической неточностью, поскольку тегируется значение указателя, а не значение данных. Большинство архитектур имеют байтовую адресуемость (наименьшая адресуемая единица — байт), но определенные типы данных часто выравниваются по размеру данных, обычно по слову или кратному ему. Это несоответствие оставляет несколько младших значащих битов указателя неиспользованными, которые можно использовать для тегов — чаще всего в виде битового поля (каждый бит представляет отдельный тег) — при условии, что код, использующий указатель, маскирует эти биты перед обращением к памяти. Например, в 32-битной архитектуре (как для адресов, так и для размера слова) слово состоит из 32 бит = 4 байта, поэтому адреса, выровненные по слову, всегда кратны 4 и заканчиваются на 00, оставляя доступными последние 2 бита; в то время как в 64-битной архитектуре слово состоит из 64 бит = 8 байт, поэтому адреса, выровненные по слову, заканчиваются на 000, оставляя доступными последние 3 бита. В случаях, когда данные выровнены по кратному размеру слова, становятся доступными дополнительные биты. В архитектурах с адресуемостью по словам, выровненные данные по слову не оставляют доступных битов, поскольку между выравниванием и адресацией нет несоответствия, но данные, выровненные по кратному размеру слова, оставляют. И наоборот, в некоторых операционных системах виртуальные адреса уже, чем общая ширина архитектуры, что оставляет старшие значащие биты для тегов; это можно комбинировать с предыдущим методом в случае выровненных адресов. Это особенно актуально для 64-битных архитектур, поскольку 64-битное адресное пространство значительно превышает требования к данным для всех приложений, кроме самых крупных, и поэтому многие практические 64-битные процессоры имеют более узкие адреса. Следует отметить, что ширина виртуального адреса может быть меньше, чем ширина физического адреса, который, в свою очередь, может быть меньше, чем ширина архитектуры; для кодирования тегов в указателях в пользовательском пространстве соответствующей является ширина виртуального адресного пространства, предоставляемого операционной системой (которое, в свою очередь, предоставляется блоком управления памятью). Фактически, некоторые процессоры специально запрещают использование таких помеченных указателей на уровне процессора, в частности x86-64, который требует от операционной системы использования канонических адресов, причем старшие значащие биты либо все 0, либо все 1. Наконец, система виртуальной памяти в большинстве современных операционных систем резервирует блок логической памяти вокруг адреса 0 как недоступный. Это означает, что, например, указатель на 0 никогда не является допустимым указателем и может использоваться в качестве специального значения нулевого указателя. В отличие от вышеупомянутых методов, это позволяет использовать только одно специальное значение указателя, а не дополнительные данные для указателей в целом.

Примеры

Одним из самых ранних примеров аппаратной поддержки помеченных указателей в коммерческой платформе была IBM System/38. Позднее IBM добавила поддержку помеченных указателей в архитектуру PowerPC для поддержки операционной системы IBM i, являющейся развитием платформы System/38. Важным примером использования помеченных указателей является среда выполнения Objective-C в iOS 7 на ARM64, используемая, в частности, в iPhone 5S. В iOS 7 виртуальные адреса содержат всего 33 бита адресной информации, но имеют длину 64 бита, оставляя 31 бит для тегов. Указатели классов Objective-C выровнены по 8 байтам, что освобождает дополнительные 3 бита адресного пространства, а поля тегов используются для различных целей, таких как хранение счетчика ссылок и информации о наличии у объекта деструктора. В ранних версиях macOS использовались помеченные адреса, называемые дескрипторами (Handles), для хранения ссылок на объекты данных. Старшие биты адреса указывали, заблокирован ли объект данных, подлежит ли он очистке и/или был ли он получен из файла ресурсов, соответственно. Это привело к проблемам совместимости, когда адресация macOS перешла с 24 бит до 32 бит в System 7.

Нуль против выровненного указателя

Использование нуля для представления нулевого указателя чрезвычайно распространено, и многие языки программирования (например, Ada) явно полагаются на такое поведение. В теории, другие значения в зарезервированном блоке логической памяти операционной системы могли бы использоваться для обозначения условий, отличных от нулевого указателя, но такие случаи представляются редкими, вероятно, из-за их низкой переносимости. Общепринятой практикой в разработке программного обеспечения является то, что если требуется специальное значение указателя, отличное от нулевого (например, сторожевой указатель в определенных структурах данных), программист должен явно его предусмотреть. Использование выравнивания указателей предоставляет большую гибкость, чем нулевые указатели/стражи, поскольку позволяет снабжать указатели информацией о типе данных, на которые они указывают, условиях доступа к этим данным или другой подобной информацией об использовании указателя. Эта информация может сопровождать каждый допустимый указатель. В отличие от этого, нулевые указатели/стражи предоставляют лишь конечное число помеченных значений, отличных от допустимых указателей. В архитектуре с тегами, часть битов в каждом слове памяти резервируется для использования в качестве тега. Архитектуры с тегами, такие как машины Lisp, часто имеют аппаратную поддержку для интерпретации и обработки помеченных указателей. GNU libc malloc предоставляет 8-байтовую адресацию для 32-битных платформ и 16-байтовую для 64-битных. Более высокие значения выравнивания можно получить с помощью posix memalign.

Преимущества

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

Недостатки

У помеченных указателей есть некоторые из тех же сложностей, что и у списков, связанных посредством исключающего ИЛИ, хотя и в меньшей степени. Например, не все отладчики смогут правильно отслеживать помеченные указатели; однако это не является проблемой для отладчика, разработанного с учетом помеченных указателей. Использование нуля для представления нулевого указателя не имеет этих недостатков: оно широко распространено, большинство языков программирования рассматривают ноль как специальное нулевое значение, и оно хорошо зарекомендовало себя в плане надежности. Исключением является участие нуля в разрешении перегрузок в C++, где ноль интерпретируется как целое число, а не как указатель; по этой причине специальное значение `nullptr` предпочтительнее целочисленного нуля. Однако при использовании помеченных указателей нули обычно не применяются для представления нулевых указателей.