Тэгированный указатель — указатель (а именно, адрес памяти) с дополнительными данными, ассоциированными с ним, такими как принак косвенного указателя или счётчик ссылок. Эти дополнительные данные часто «встраиваются» в указатель, то есть хранится непосредственно в данных, представляющих адрес, используя некоторые особенности адресного пространства. Термин происходит от систем с «тэговой архитектрой», в которых на аппаратном уровне резервируются биты для обозначения значимости каждого слова. Дополнительные данные называются «тэгом» или «тэгами», хотя, строго говоря, «тэг» относится к данным, определяющим тип, а не к дополнительным данным. Тем не менее, термин «тэгированный указатель» употребляется широко.
Встраивание тэгов в указатель Имеется несколько различных техник встраивания тэгов в указатель. Большинство архитектур имеют (наименшим адресуемым элементом является байт), но некоторые типы данных часто выровнены по размеру данных, часто это слово или кратное им. Это несоответствие приводит к тому, что несколько указателя остаются неиспользуемыми. Эти биты можно использовать в качестве тэгов, чаще всего в виде битового поля (каждый бит представляет отдельный флаг) при условии, что использующий указатель код обнуляет эти биты перед доступом к памяти. Например, в 32-битной архитектуре (как для адресов, так и для размера слова) слово составляет 32 бита = 4 байтам. Следовательно, адреса, выровненные по слову, всегда кратны 4 и заканчиваются на 00, оставляя два младших бита свободными. В 64-битной архитектуре слово составляет 64 бита = 8 байт, поэтому адреса, выровненные по слову, заканчиваются на 000, оставляя три младших бита свободными. В случаях, когда данные выравниваются по кратному размеру слова, доступны дополнительные биты. В случае архитектуры с выровненные данные не оставляют незадействованных бит, поскольку нет расхождения между выравниванием и адресацией. Однако данные, выровненные по кратному слову размеру свободные биты оставляют.
В некоторых операционных системах виртуальные адреса имеют меньшую разрядность, чем общая архитектура шины, что оставляет для использования в качестве тэгов. Данный подход можно объединить с предыдущим методом для выровненных адресов. Такая ситуация особенно характерна для 64-битных архитектур, поскольку 64 бита адреса значительно превышают потребности для всех приложений, кроме самых больших. Многие практические 64-битные процессоры используют адреса меньшей разрядности. Ширина виртуального адреса может быть меньше, чем ширина , который, в свою очередь, может быть уже архитектурной ширины. Некоторые процессоры запрещают использование тегированных указателей (хоть и не используют старших бит). В частности, процессоры x86-64 требуют канонической формы адреса, в которой старшие биты либо нули, либо единицы.
Система виртуальной памяти в большинстве современных операционных систем резервирует блок логической памяти около адреса 0 как неиспользуемый. Это означает, например, что указатель 0 не является никогда допустимым указателем и может быть использована как специальное значение «нулевой указатель». В отличие от предыдущих упомянутых техник это позволяет одно единственное значение указателя, а не дополнительные данные для общего указателя.
Примеры Одним из первых примеров аппаратной поддержки тегированных указателей на коммерческой платформе была IBM System/38. IBM позднее добавила поддержку тэгированного указателя в архитектуру PowerPC для поддержки операционной системы IBM i, которая является развитием платформы System/38.
Примером использования тэгированных указателей является библиотека среды выполнения Objective-C в iOS 7 на архитектуре ARM64, используемой, в частности, в iPhone 5S. В iOS 7 виртуальные адреса содержат только 33 бита адресной информации, оставляя 31 бит от 64 бит для тэгов. Objective-C указатели классов являются 8-байтными выровненными указателями, освобождая дополнительно 3 бита адресного пространства. Эти битовые поля тегов используются для различных целей, например, для хранения счётчика ссылок и информации о наличии деструктора у объекта деструктора.
Ранние версии macOS использовали тэгированные указатели на объекты данных, называемые Handles (Обработчики). Старшие биты адреса показывали, является ли объект данных заблокированным, выгруженным и/или получен из ресурсного файла. Это привело к проблемам совместимости, когда macOS адресация перешла от 24 бит к 32 битам в System 7.
Null против выровненного указателя Использование нуля для null (неопределённого) указателя встречается чрезвычайно часто, причём многие языки программирования (например, Ada) явно полагаются на такое поведение. Теоретически, другие значения из зарезервированного операционной системой блока логической памяти могли бы использоваться для обозначения состояний, отличных от null указателя, но такие применения встречаются редко, возможно, потому, что они, в лучшем случае, непереносимы. В практике проектирования программного обеспечения общепринято, что если требуется специальное значение указателя, отличное от null (например, в определённых структурах данных), программист должен явно предусмотреть его.
Использование выравнивания указателей даёт больше гибкости, чем применение null указателей или сторожевых значений, поскольку позволяет помечать указатели информацией о типе данных, на которые они указывают, об условиях доступа к этим данным или другой подобной информацией об использовании указателя. Эта информация может быть предоставлена вместе с каждым допустимым указателем. В отличие от этого, нулевые указатели или сторожевые значения предоставляют лишь ограниченное количество помеченных значений, отличных от допустимых указателей.
В тэговой архитектре некоторое число бит каждого слова памяти зарезервировано под тэги. Тэговые архитектуры, например, лисп-машины, часто имеют аппаратную поддержку для интерпретации и обработки тэгированных указателей.
GNU libc функция malloc() даёт выровненные на границу 8 байт адреса памяти для 32-битных платформ, и выровненные на границу 16 байт адреса для 64-битных платформ. Бо́льшие значения выравнивания можно получить с помощью posix_memalign().
Примеры
Пример 1 В следующем примере кода на языке C значение нуль используется для обозначения null (неопределённого) указателя:
void optionally_return_a_value (int* optional_return_value_pointer) { /* ... */ int value_to_return = 1;
/* is it non-NULL? (note that NULL, logical false, and zero compare equally in C) */ if (optional_return_value_pointer) /* if so, use it to pass a value to the calling function */ *optional_return_value_pointer = value_to_return;
/* otherwise, the pointer is never dereferenced */ }
Пример 2 Здесь программист использует глобальную переменную, адрес которой используется как страж (sentinel):
# define SENTINEL &sentinel_s
node_t sentinel_s;
void do_something_to_a_node (node_t * p) { if (NULL == p) /* do something */ else if (SENTINEL == p) /* do something else */ else /* treat p as a valid pointer to a node */ }
Пример 3 Предположим, что у нас есть структура данных table_entry, элементы которой всегда выровнены на границу 16 байт. Другими словами, младшие 4 бита указателей в таблице всегда равны 0 Мы можем использовать эти 4 бита для помещения дополнительной информации. Например, бит 0 может содержать «только для чтения», бит 1 может означать «dirty» (элемент таблицы должен быть обновлён), и так далее.
Если указатели являются 16-битными: 0x3421 указатель «только для чтения» на table_entry по адресу 0x3420 0xf472 указатель «dirty» на table_entry по адресу 0xf470
Преимущества Главное преимущество тэгированных указателей в том, что они занимают меньше места, чем отдельный указатель и поле для тэга. Это может быть особенно важно, когда указатель является возвращаемым значением функции. Также это может иметь значение при работе с большими таблицами указателей.
Более тонкое преимущество состоит в том, что, храня метку в том же месте, что и указатель, часто удаётся гарантировать атомарность операции, обновляющей как указатель, так и его метку, без использования внешних механизмов синхронизации. Это может обеспечить значительный прирост производительности, особенно в операционных системах.
Недостатки Тегированные указатели имеют некоторые общие проблемы с XOR-связными списками, хотя и в меньшей степени. Например, не все отладчики способны правильно обрабатывать тегированные указатели, однако это не проблема для отладчика, разработанного с учётом тегированных указателей.
Использование нуля для представления null указателя лишено этих недостатков, оно повсеместно, и большинство языков программирования рассматривают ноль как специальное null значение, и оно доказало свою надёжность. Исключением составляет ситуация, когда ноль участвует в перегрузке процедур и функций в C++, где нуль трактуется как целое, а не указатель. По этой причине специальное значение nullptr предпочтительнее. Однако, при использовании тегированных указателей нули обычно не применяются для представления нулевых указателей.
Примечания
Литература
Категория:Программные конструкции Категория:Указатели (программирование)