En informática , un puntero etiquetado es un puntero (concretamente una dirección de memoria ) con datos adicionales asociados, como un bit de indirección o un recuento de referencia . Estos datos adicionales suelen estar "plegados" en el puntero, es decir, almacenados en línea en los datos que representan la dirección, aprovechando ciertas propiedades del direccionamiento de memoria. El nombre proviene de los sistemas de " arquitectura etiquetada ", que reservaban bits a nivel de hardware para indicar el significado de cada palabra; los datos adicionales se denominan "etiqueta" o "etiquetas", aunque estrictamente hablando "etiqueta" se refiere a datos que especifican un tipo, no a otros datos; sin embargo, el uso de "puntero etiquetado" es omnipresente.
Existen varias técnicas para plegar etiquetas y formar un puntero. [1] [ ¿ fuente poco confiable? ]
La mayoría de las arquitecturas son direccionables por byte (la unidad direccionable más pequeña es un byte), pero ciertos tipos de datos a menudo se alinearán al tamaño de los datos, a menudo una palabra o un múltiplo de la misma. Esta discrepancia deja algunos de los bits menos significativos del puntero sin usar, que se pueden usar para etiquetas, generalmente como un campo de bits (cada bit es una etiqueta separada), siempre que el código que usa el puntero enmascare estos bits antes de acceder a la memoria. Por ejemplo, en una arquitectura de 32 bits (tanto para direcciones como para tamaño de palabra), una palabra es 32 bits = 4 bytes, por lo que las direcciones alineadas por palabra siempre son múltiplos de 4, por lo tanto terminan en 00, dejando los últimos 2 bits disponibles; mientras que en una arquitectura de 64 bits , una palabra es 64 bits = 8 bytes, por lo que las direcciones alineadas por palabra terminan en 000, dejando los últimos 3 bits disponibles. En los casos en que los datos se alinean en un múltiplo del tamaño de palabra, hay más bits disponibles. En el caso de arquitecturas direccionables por palabra , los datos alineados por palabra no dejan ningún bit disponible, ya que no hay discrepancia entre la alineación y el direccionamiento, pero los datos alineados en un múltiplo del tamaño de la palabra sí la tienen.
Por el contrario, en algunos sistemas operativos, las direcciones virtuales son más estrechas que el ancho general de la arquitectura, lo que deja los bits más significativos disponibles para las etiquetas; esto se puede combinar con la técnica anterior en el caso de direcciones alineadas. Este es particularmente el caso en arquitecturas de 64 bits, ya que 64 bits de espacio de direcciones están muy por encima de los requisitos de datos de todas las aplicaciones, excepto las más grandes, y por lo tanto, muchos procesadores prácticos de 64 bits tienen direcciones más estrechas. Tenga en cuenta que el ancho de la dirección virtual puede ser más estrecho que el ancho de la dirección física , que a su vez puede ser más estrecho que el ancho de la arquitectura; para el etiquetado de punteros en el espacio de usuario , el espacio de direcciones virtuales proporcionado por el sistema operativo (a su vez proporcionado por la unidad de gestión de memoria ) es el ancho relevante. De hecho, algunos procesadores prohíben específicamente el uso de dichos punteros etiquetados a nivel de procesador, en particular x86-64 , que requiere el uso de direcciones de forma canónica por parte del sistema operativo, con los bits más significativos todos 0 o todos 1.
Por último, el sistema de memoria virtual de la mayoría de los sistemas operativos modernos reserva un bloque de memoria lógica alrededor de la dirección 0 como inutilizable. Esto significa que, por ejemplo, un puntero a 0 nunca es un puntero válido y se puede utilizar como un valor de puntero nulo especial . A diferencia de las técnicas mencionadas anteriormente, esto solo permite un único valor de puntero especial, no datos adicionales para los punteros en general.
Uno de los primeros ejemplos de soporte de hardware para punteros etiquetados en una plataforma comercial fue el IBM System/38 . [2] Posteriormente, IBM agregó soporte para punteros etiquetados a la arquitectura PowerPC para soportar el sistema operativo IBM i , que es una evolución de la plataforma System/38. [3]
Un ejemplo significativo del uso de punteros etiquetados es el entorno de ejecución Objective-C en iOS 7 en ARM64 , utilizado en particular en el iPhone 5S . En iOS 7, las direcciones virtuales solo contienen 33 bits de información de dirección, pero tienen una longitud de 64 bits, lo que deja 31 bits para las etiquetas. Los punteros de clase Objective-C están alineados en 8 bytes, lo que libera 3 bits adicionales de espacio de dirección, y los campos de etiqueta se utilizan para muchos propósitos, como almacenar un recuento de referencias y si el objeto tiene un destructor . [4] [5]
Las primeras versiones de macOS usaban direcciones etiquetadas llamadas Handles para almacenar referencias a objetos de datos. Los bits altos de la dirección indicaban si el objeto de datos estaba bloqueado, purgable o se originaba a partir de un archivo de recursos, respectivamente. Esto causó problemas de compatibilidad cuando el direccionamiento de macOS avanzó de 24 bits a 32 bits en System 7. [6]
El uso de cero para representar un puntero nulo es extremadamente común, y muchos lenguajes de programación (como Ada ) dependen explícitamente de este comportamiento. En teoría, se podrían usar otros valores en un bloque de memoria lógica reservado para el sistema operativo para etiquetar condiciones distintas a un puntero nulo, pero estos usos parecen ser poco frecuentes, tal vez porque, en el mejor de los casos , no son portables . En el diseño de software, es una práctica generalmente aceptada que, si se necesita un valor de puntero especial distinto de nulo (como un centinela en ciertas estructuras de datos ), el programador debe proporcionarlo explícitamente.
Aprovechar la alineación de punteros proporciona más flexibilidad que los punteros nulos o centinelas porque permite etiquetar los punteros con información sobre el tipo de datos a los que apunta, las condiciones en las que se puede acceder a ellos u otra información similar sobre el uso del puntero. Esta información se puede proporcionar junto con cada puntero válido. Por el contrario, los punteros nulos o centinelas proporcionan solo un número finito de valores etiquetados distintos de los punteros válidos.
En una arquitectura etiquetada , se reservan una serie de bits en cada palabra de memoria para que actúen como etiquetas. Las arquitecturas etiquetadas, como las máquinas Lisp , suelen tener soporte de hardware para interpretar y procesar punteros etiquetados.
GNU libc malloc()
proporciona direcciones de memoria alineadas de 8 bytes para plataformas de 32 bits y una alineación de 16 bytes para plataformas de 64 bits. [7] Se pueden obtener valores de alineación más grandes con posix_memalign()
. [8]
En el siguiente código C, se utiliza el valor cero para indicar un puntero nulo:
void opcionalmente_devolver_un_valor ( int * puntero_de_valor_de_retorno_opcional ) { /* ... */ int valor_a_devolver = 1 ; /* ¿no es NULL? (tenga en cuenta que NULL, falso lógico y cero se comparan igualmente en C) */ if ( optional_return_value_pointer ) /* si es así, úselo para pasar un valor a la función que llama */ * optional_return_value_pointer = value_to_return ; /* de lo contrario, el puntero nunca se desreferencia */ }
Aquí, el programador ha proporcionado una variable global, cuya dirección se utiliza luego como centinela:
#define CENTINELA &sentinel_snodo_t centinela_s ; void do_something_to_a_node ( node_t * p ) { if ( NULL == p ) /* hacer algo */ else if ( SENTINEL == p ) /* hacer otra cosa */ else /* tratar p como un puntero válido a un nodo */ }
Supongamos que tenemos una estructura de datos table_entry
que siempre está alineada con un límite de 16 bytes. En otras palabras, los 4 bits menos significativos de la dirección de una entrada de la tabla son siempre 0 ( 2 4 = 16 ). Podríamos usar estos 4 bits para marcar la entrada de la tabla con información adicional. Por ejemplo, el bit 0 podría significar solo lectura, el bit 1 podría significar que está sucia (la entrada de la tabla necesita actualizarse), y así sucesivamente.
Si los punteros son valores de 16 bits, entonces:
0x3421
es un puntero de solo lectura a la table_entry
dirección at0x3420
0xf472
es un puntero a una table_entry
dirección sucia0xf470
La principal ventaja de los punteros etiquetados es que ocupan menos espacio que un puntero con un campo de etiqueta independiente. Esto puede ser especialmente importante cuando un puntero es un valor de retorno de una función . También puede ser importante en tablas grandes de punteros.
Una ventaja más sutil es que al almacenar una etiqueta en el mismo lugar que el puntero, a menudo es posible garantizar la atomicidad de una operación que actualiza tanto el puntero como su etiqueta sin mecanismos de sincronización externos . [ se necesita más explicación ] Esto puede suponer una ganancia de rendimiento extremadamente grande, especialmente en sistemas operativos.
Los punteros etiquetados tienen algunas de las mismas dificultades que las listas enlazadas xor , aunque en menor medida. Por ejemplo, no todos los depuradores podrán seguir correctamente los punteros etiquetados; sin embargo, esto no es un problema para un depurador que está diseñado teniendo en cuenta los punteros etiquetados.
El uso del cero para representar un puntero nulo no sufre estas desventajas: es generalizado, la mayoría de los lenguajes de programación tratan al cero como un valor nulo especial y ha demostrado ampliamente su robustez. Una excepción es la forma en que el cero participa en la resolución de sobrecarga en C++, donde el cero se trata como un entero en lugar de un puntero; por esta razón, se prefiere el valor especial nullptr al entero cero. Sin embargo, con punteros etiquetados, los ceros generalmente no se utilizan para representar punteros nulos.