Articulo de referencia

Puntero (programación informática)

{{cite journal|author-link=Donald Knuth|author-last1=Knuth|author-first1=Donald|title=Structured Programming with go to Statements |journal=Computing Surveys |volume=6 |issue=5 ...

Considero que las sentencias de asignación y las variables de puntero se encuentran entre los "tesoros más valiosos" de la informática.

Donald Knuth , Programación Estructurada, con instrucciones go to [ 1 ]
Un puntero aque apunta a la dirección de memoria asociada a una variable b, es decir, acontiene la dirección de memoria 1008 de la variable b. En este diagrama, la arquitectura de computación utiliza el mismo espacio de direcciones y primitiva de datos tanto para punteros como para objetos que no son punteros; esto no tiene por qué ser así.

En informática , un puntero es un objeto en muchos lenguajes de programación que almacena una dirección de memoria . Esta puede ser la de otro valor ubicado en la memoria del ordenador o, en algunos casos, la de hardware del ordenador mapeado en memoria . Un puntero hace referencia a una ubicación en la memoria, y obtener el valor almacenado en esa ubicación se conoce como desreferenciar el puntero. A modo de analogía, el número de página en el índice de un libro podría considerarse un puntero a la página correspondiente; para desreferenciar dicho puntero, bastaría con ir a la página con el número de página indicado y leer el texto que contiene. El formato y el contenido de una variable de puntero dependen de la arquitectura del ordenador subyacente .

El uso de punteros mejora significativamente el rendimiento en operaciones repetitivas, como el recorrido de estructuras de datos iterables (por ejemplo , cadenas de caracteres , tablas de búsqueda , tablas de control , listas enlazadas y estructuras de árbol ). En particular, suele ser mucho más económico, en términos de tiempo y espacio, copiar y desreferenciar punteros que copiar y acceder a los datos a los que apuntan.

Los punteros también se utilizan para almacenar las direcciones de los puntos de entrada de las subrutinas llamadas en la programación procedimental y para la vinculación en tiempo de ejecución con bibliotecas de vínculos dinámicos (DLL) . En la programación orientada a objetos , los punteros a funciones se utilizan para vincular métodos , a menudo mediante tablas de métodos virtuales .

Un puntero es una implementación simple y más concreta del tipo de datos de referencia más abstracto . Varios lenguajes, especialmente los de bajo nivel , admiten algún tipo de puntero, aunque algunos tienen más restricciones en su uso que otros. Si bien "puntero" se ha utilizado para referirse a referencias en general, se aplica más apropiadamente a estructuras de datos cuya interfaz permite explícitamente que el puntero sea manipulado (aritméticamente a través dearitmética de punteros ) como una dirección de memoria, a diferencia de unacookie mágicaocapacidadque no lo permite.Debido a que los punteros permiten tanto el acceso protegido como el no protegido alas direcciones de memoria, existen riesgos asociados con su uso, particularmente en el segundo caso. Los punteros primitivos a menudo se almacenan en un formato similar a unentero; sin embargo, intentar desreferenciar o "buscar" un puntero cuyo valor no sea una dirección de memoria válida podría provocar que un programafalle(o contenga datos no válidos). Para mitigar este problema potencial, por una cuestión deseguridad de tipos, los punteros se consideran un tipo separado parametrizado por el tipo de datos a los que apuntan, incluso si la representación subyacente es un entero. También se pueden tomar otras medidas (comovalidaciónycomprobación de límites) para verificar que la variable de puntero contenga un valor que sea tanto una dirección de memoria válida como dentro del rango numérico que el procesador puede direccionar.

Historia

En 1955, la científica informática soviética ucraniana Kateryna Yushchenko creó el lenguaje de programación Address , que posibilitó el direccionamiento indirecto y las direcciones de rango superior, análogas a los punteros. Este lenguaje se utilizó ampliamente en las computadoras soviéticas . Sin embargo, era desconocido fuera de la Unión Soviética y, por lo general, se le atribuye a Harold Lawson la invención del puntero en 1964. [ 2 ] En 2000, Lawson recibió el Premio Pionero de la Informática del IEEE "[p]or inventar la variable puntero e introducir este concepto en PL/I, proporcionando así, por primera vez, la capacidad de tratar de forma flexible las listas enlazadas en un lenguaje de alto nivel de propósito general". [ 3 ] Su artículo fundamental sobre los conceptos apareció en el número de junio de 1967 de CACM titulado: PL/I List Processing. Según el Oxford English Dictionary , la palabra puntero apareció por primera vez impresa como puntero de pila en un memorando técnico de System Development Corporation .

Descripción formal

En informática , un puntero es un tipo de referencia .

Un dato primitivo (o simplemente primitivo ) es cualquier dato que se puede leer o escribir en la memoria de la computadora utilizando un solo acceso a la memoria (por ejemplo, tanto un byte como una palabra son datos primitivos).

Un agregado de datos (o simplemente agregado ) es un grupo de primitivas lógicamente contiguas en memoria que se consideran colectivamente como un solo dato (por ejemplo, un agregado podría ser de 3 bytes lógicamente contiguos, cuyos valores representan las 3 coordenadas de un punto en el espacio). Cuando un agregado está compuesto enteramente por el mismo tipo de primitiva, se le puede llamar matriz ; en cierto modo, una palabra primitiva multibyte es una matriz de bytes, y algunos programas utilizan palabras de esta manera.

En el contexto de estas definiciones, un byte es la unidad básica; cada dirección de memoria especifica un byte diferente. La dirección de memoria del byte inicial de un dato se considera la dirección de memoria (o dirección de memoria base ) de todo el dato.

Un puntero de memoria (o simplemente puntero ) es un tipo de dato primitivo cuyo valor se utiliza como dirección de memoria; se dice que un puntero apunta a una dirección de memoria . También se dice que un puntero apunta a un dato [en memoria] cuando el valor del puntero es la dirección de memoria de dicho dato.

En términos más generales, un puntero es un tipo de referencia , y se dice que un puntero apunta a un dato almacenado en la memoria ; para obtener ese dato, se desreferencia el puntero . La característica que distingue a los punteros de otros tipos de referencia es que el valor de un puntero se interpreta como una dirección de memoria, un concepto de bajo nivel.

Las referencias sirven como un nivel de indirección: el valor de un puntero determina qué dirección de memoria (es decir, qué dato) se utilizará en un cálculo. Dado que la indirección es un aspecto fundamental de los algoritmos, los punteros suelen expresarse como un tipo de dato fundamental en los lenguajes de programación ; en los lenguajes de programación con tipado estático (o fuertemente tipado ), el tipo de un puntero determina el tipo del dato al que apunta.

Raíces arquitectónicas

Los punteros constituyen una abstracción muy simple sobre las capacidades de direccionamiento que ofrecen la mayoría de las arquitecturas modernas . En el esquema más sencillo, se asigna una dirección , o un índice numérico , a cada unidad de memoria del sistema, que suele ser un byte o una palabra (dependiendo de si la arquitectura admite direccionamiento por bytes o por palabras ), transformando así toda la memoria en una matriz muy grande . El sistema también proporciona una operación para recuperar el valor almacenado en la unidad de memoria en una dirección determinada (generalmente utilizando los registros de propósito general de la máquina ).

En el caso habitual, un puntero es lo suficientemente grande como para almacenar más direcciones que unidades de memoria en el sistema. Esto introduce la posibilidad de que un programa intente acceder a una dirección que no corresponde a ninguna unidad de memoria, ya sea porque no hay suficiente memoria instalada (es decir, más allá del rango de memoria disponible) o porque la arquitectura no admite dichas direcciones. El primer caso puede, en ciertas plataformas como la arquitectura Intel x86 , denominarse fallo de segmentación (segfault). El segundo caso es posible en la implementación actual de AMD64 , donde los punteros tienen 64 bits de longitud y las direcciones solo se extienden hasta 48 bits. Los punteros deben ajustarse a ciertas reglas (direcciones canónicas), por lo que si se desreferencia un puntero no canónico, el procesador genera un fallo de protección general .

Por otro lado, algunos sistemas tienen más unidades de memoria que direcciones. En este caso, se emplea un esquema más complejo, como la segmentación o paginación de memoria, para utilizar diferentes partes de la memoria en distintos momentos. Las últimas versiones de la arquitectura x86 admiten hasta 36 bits de direcciones de memoria física, que se asignaban al espacio de direcciones lineal de 32 bits mediante el mecanismo de paginación PAE . Por lo tanto, solo se puede acceder a 1/16 de la memoria total disponible a la vez. Otro ejemplo en la misma familia de computadoras fue el modo protegido de 16 bits del procesador 80286 , que, aunque solo admitía 16 MB de memoria física, podía acceder hasta 1 GB de memoria virtual, pero la combinación de registros de direcciones y segmentos de 16 bits hacía que el acceso a más de 64 KB en una sola estructura de datos fuera engorroso.

Para proporcionar una interfaz consistente, algunas arquitecturas ofrecen E/S mapeada en memoria , lo que permite que algunas direcciones hagan referencia a unidades de memoria, mientras que otras hacen referencia a registros de dispositivos de otros dispositivos en la computadora. Existen conceptos análogos, como desplazamientos de archivo, índices de matriz y referencias a objetos remotos, que cumplen funciones similares a las de las direcciones para otros tipos de objetos.

Usos

Los punteros son compatibles directamente y sin restricciones en lenguajes como PL/I , C , C++ , Pascal , FreeBASIC , e implícitamente en la mayoría de los lenguajes ensamblador . Se utilizan principalmente para construir referencias , que a su vez son fundamentales para construir casi todas las estructuras de datos , y para pasar datos entre diferentes partes de un programa.

En los lenguajes de programación funcional que dependen en gran medida de las listas, las referencias a datos se gestionan de forma abstracta mediante construcciones primitivas como `cons` y los elementos correspondientes `car` y `cdr` , que pueden considerarse punteros especializados al primer y segundo componente de una celda `cons`. Esto da lugar a parte del estilo característico de la programación funcional. Al estructurar los datos en dichas listas `cons` , estos lenguajes facilitan los métodos recursivos para construir y procesar datos; por ejemplo, accediendo recursivamente a los elementos de cabeza y cola de listas de listas; por ejemplo, "tomando el `car` del `cdr` del `cdr`". Por el contrario, la gestión de memoria basada en la desreferenciación de punteros en una aproximación a un array de direcciones de memoria facilita el tratamiento de las variables como ranuras en las que se pueden asignar datos de forma imperativa .

Al trabajar con arreglos, la operación de búsqueda crítica generalmente implica una etapa llamada cálculo de direcciones , que consiste en construir un puntero al elemento de datos deseado en el arreglo. En otras estructuras de datos, como las listas enlazadas , los punteros se utilizan como referencias para vincular explícitamente una parte de la estructura con otra.

Los punteros se utilizan para pasar parámetros por referencia. Esto resulta útil si el programador desea que las modificaciones que una función realice en un parámetro sean visibles para quien la llama. También es útil para devolver múltiples valores desde una función.

Los punteros también se pueden usar para asignar y liberar variables y arreglos dinámicos en memoria. Dado que una variable suele volverse redundante una vez que ha cumplido su función, conservarla supone un desperdicio de memoria; por lo tanto, es recomendable liberarla (usando la referencia del puntero original) cuando ya no sea necesaria. No hacerlo puede provocar una fuga de memoria (donde la memoria libre disponible disminuye gradualmente, o en casos graves, rápidamente, debido a la acumulación de numerosos bloques de memoria redundantes).

Punteros de C

En C , la sintaxis básica para definir un puntero es: [ 4 ]

int * puntero ;

Esto declara una variable ptrque almacena un puntero a un objeto de tipo int. Se pueden usar otros tipos en lugar de int; por ejemplo, bool *ptrdeclararía un puntero a un objeto de tipo bool.

Una vez declarado un puntero, se le puede asignar una dirección. En C, la dirección de una variable se puede obtener con el &operador unario :

// Declara una variable para un puntero a un entero int * ptr ; // Declara una variable para un entero int a = 5 ; // Asigna la dirección de a a la variable de puntero ptr = &a a ;

*Para desreferenciar el puntero y obtener el objeto al que apunta, se puede usar un asterisco ( *):

printf ( "%d \n " , * ptr ); // Imprime: 5

Existen dos estilos de declaración de un puntero a un objeto intcon nombre ptr:

  • int*ptr;, lo que enfatiza que la expresión *ptrtiene tipo int. [ 5 ]
  • int*ptr;, que enfatiza que ptrtiene tipo int*. Este estilo es más frecuente en C++ que en C. [ 6 ] [ 7 ]

Tanto como declaran ser un puntero a , pero declara ser un . Esto se debe a que se aplica solo a la variable que le sigue inmediatamente. [ 5 ]int*x,y;int*x,y;xintyint*

El asterisco también se puede usar en el lado izquierdo de una asignación para cambiar el objeto asin tener que estar en el mismo ámbito.

* puntero = 8 ;

Si ase accede a él más tarde, su nuevo valor será 8.

Debido a que el lenguaje C no especifica una inicialización implícita para objetos de duración de almacenamiento automático, [ 8 ] las variables de puntero a veces pueden apuntar a ubicaciones inesperadas, causando un comportamiento indefinido. Para combatir esto, los punteros a veces se inicializan con un valor de puntero nulo , representado en C por la NULLmacro; [ 9 ] en C23 y posteriores, nullptrtambién está disponible como alternativa, que es de tipo nullptr_t. [ 10 ]

int * ptr = NULL ; int * ptr = nullptr ; // desde C23

La desreferenciación de un puntero nulo produce un comportamiento indefinido , [ 11 ] que puede generar errores y resultados impredecibles.

Este ejemplo puede resultar más claro si se examina la memoria directamente. Supongamos que ase encuentra en la dirección 0x8130 de la memoria y ptren la 0x8134; supongamos también que se trata de una máquina de 32 bits, de modo que un entero tiene 32 bits de ancho. Lo siguiente es lo que habría en la memoria después de ejecutar el siguiente fragmento de código:

int a = 5 ; int * ptr = NULL ;

(El puntero nulo que se muestra aquí es 0x00000000 .) Al asignar la dirección de a :aptr

puntero = &a a ;

produce los siguientes valores de memoria:

Luego, mediante la desreferenciación ptrpor codificación:

* puntero = 8 ;

La computadora tomará el contenido de ptr(que es 0x8130), 'localizará' esa dirección y asignará 8 a esa ubicación, lo que dará como resultado la siguiente memoria:

Claramente, el acceso adará como resultado el valor 8 porque la instrucción anterior modificó el contenido aa través del puntero ptr.

Uso en estructuras de datos

Al configurar estructuras de datos como listas , colas y árboles, es necesario contar con punteros para gestionar su implementación y control. Ejemplos típicos de punteros son los punteros de inicio, los punteros de fin y los punteros de pila . Estos punteros pueden ser absolutos (la dirección física real o una dirección virtual en la memoria virtual ) o relativos (un desplazamiento desde una dirección de inicio absoluta ("base") que generalmente utiliza menos bits que una dirección completa, pero que suele requerir una operación aritmética adicional para su resolución).

Las direcciones relativas son una forma de segmentación manual de memoria y comparten muchas de sus ventajas y desventajas. Un desplazamiento de dos bytes, que contiene un entero sin signo de 16 bits, puede utilizarse para proporcionar direccionamiento relativo para hasta 64 KiB ( 2¹⁶ bytes) de una estructura de datos. Esto puede extenderse fácilmente a 128, 256 o 512 KiB si la dirección a la que apunta se alinea con un límite de media palabra, palabra o doble palabra (pero, requiriendo una operación adicional de "desplazamiento a la izquierda" —de 1, 2 o 3 bits— para ajustar el desplazamiento por un factor de 2, 4 u 8, antes de sumarle a la dirección base ). Sin embargo, en general, estos esquemas son muy engorrosos, y para mayor comodidad del programador se prefieren las direcciones absolutas (y subyacentemente, un espacio de direcciones plano ).

Un desplazamiento de un byte, como el valor ASCII hexadecimal de un carácter (por ejemplo, X'29'), puede utilizarse para apuntar a un valor entero alternativo (o índice) en una matriz (por ejemplo, X'01'). De esta forma, los caracteres pueden traducirse de manera muy eficiente de " datos sin procesar " a un índice secuencial utilizable y, posteriormente, a una dirección absoluta sin necesidad de una tabla de búsqueda .

Matrices de C

En C, la indexación de matrices se define formalmente en términos de aritmética de punteros; es decir, la especificación del lenguaje requiere que a[i]sea equivalente a *(a + i), [ 12 ] porque, si aes el nombre de una matriz, en la mayoría de los contextos la expresión ase evalúa como un puntero al primer elemento de a, [ 13 ] por lo que la sintaxis para acceder a matrices es idéntica a la que se puede usar para desreferenciar punteros. Por ejemplo, una matriz ase puede declarar y usar de la siguiente manera:

int a [ 5 ]; // Declara 5 enteros contiguos int * ptr = a ; // Los nombres de los arrays en las expresiones se evalúan como punteros en la mayoría de los casos ptr [ 0 ] = 1 ; // Los punteros se pueden indexar con la sintaxis de array * ( a + 1 ) = 2 ; // Los arrays se pueden indexar con la sintaxis de puntero * ( 1 + a ) = 2 ; // La suma de punteros es conmutativa 2 [ a ] = 4 ; // El operador de subíndice es conmutativo (quizás inusual)

Esto asigna un bloque de cinco enteros y lo nombra a; la expresión ase evalúa, en la mayoría de los casos, como un puntero al primer elemento del bloque. Otro uso común de los punteros es apuntar a memoria asignada dinámicamente mediante malloc , que devuelve un bloque de memoria consecutivo de un tamaño no menor al solicitado, que puede usarse como un arreglo.

Si bien, cuando se usa como operando para la mayoría de los operadores, un array se evalúa como un puntero al primer elemento del array, este no es el caso para el sizeofoperador. En este ejemplo, sizeof(a)se evaluará como 5 * sizeof(int)(el tamaño del array), mientras que sizeof(ptr)se evaluará como sizeof(int *), el tamaño del puntero mismo.

Los valores predeterminados de una matriz se pueden declarar de la siguiente manera:

int a [ 5 ] = { 2 , 4 , 3 , 1 , 5 };

Si ase encuentra en la memoria a partir de la dirección 0x1000 en una máquina little-endian de 32 bits, entonces la memoria contendrá lo siguiente (los valores están en hexadecimal , al igual que las direcciones):

Aquí se representan cinco números enteros: 2, 4, 3, 1 y 5. Estos cinco números enteros ocupan 32 bits (4 bytes) cada uno, almacenándose primero el byte menos significativo (esta es una arquitectura de CPU little-endian ) y se almacenan consecutivamente a partir de la dirección 0x1000 .

La sintaxis para C con punteros es:

  • asignifica 0x1000 ;
  • a + 1significa 0x1004 : el "+ 1" significa agregar el tamaño de 1 , que son 4 bytes;int
  • *asignifica desreferenciar el contenido de a. Considerando el contenido como una dirección de memoria ( 0x1000 ), busque el valor en esa ubicación ( 0x0002 );
  • a[i]significa número de elemento i, basado en 0, que ase traduce en *(a + i).

El último ejemplo muestra cómo acceder al contenido de a. Desglosándolo:

  • a + ies la ubicación de memoria del ienésimo elemento de a, comenzando en i = 0;
  • *(a + i)toma esa dirección de memoria y la desreferencia para acceder al valor.

Lista enlazada C

A continuación se muestra un ejemplo de definición de una lista enlazada en C.

struct LinkedList { void * data ; // datos de este enlace struct LinkedList * next ; // siguiente enlace; NULL si no hay ninguno };

Esta definición recursiva de punteros es esencialmente la misma que la definición recursiva de referencias del lenguaje Haskell :

datos ListaEnlazada a = Nil | Cons a ( ListaEnlazada a )

donde Niles la lista vacía, y Cons a (LinkedList a)es una celda consa de tipo con otro enlace también de tipo a.

Sin embargo, la definición con referencias se verifica mediante comprobación de tipos y no utiliza valores de señal que puedan generar confusión. Por este motivo, las estructuras de datos en C suelen manejarse mediante funciones envoltorio , cuya corrección se comprueba minuciosamente.

Paso por dirección usando punteros

Los punteros se pueden usar para pasar variables por su dirección, lo que permite cambiar su valor. Por ejemplo, considere el siguiente código C :

// Se puede cambiar una copia del entero n dentro de la función sin afectar al código que la llama. void passByValue ( int n ) { n = 12 ; }// En su lugar, se pasa un puntero m. No se crea ninguna copia del valor al que apunta m. void passByAddress ( int * m ) { * m = 14 ; }int main ( void ) { int x = 3 ;// Pasar una copia del valor de x como argumento passByValue ( x ); // El valor se modificó dentro de la función, pero x sigue siendo 3 a partir de ahora.// Pasar la dirección de x como argumento passByAddress ( & x ); // x fue modificado por la función y ahora es igual a 14 aquídevolver 0 ; }

Asignación dinámica de memoria

En algunos programas, la cantidad de memoria requerida depende de lo que el usuario ingrese. En estos casos, el programador necesita asignar memoria dinámicamente. Esto se logra asignando memoria en el montón (heap) en lugar de en la pila (stack ), donde generalmente se almacenan las variables (aunque también pueden almacenarse en los registros de la CPU). La asignación dinámica de memoria solo se puede realizar mediante punteros, y no se les pueden asignar nombres , al igual que a las variables comunes .

Los punteros se utilizan para almacenar y gestionar las direcciones de bloques de memoria asignados dinámicamente . Dichos bloques se utilizan para almacenar objetos de datos o matrices de objetos. La mayoría de los lenguajes estructurados y orientados a objetos proporcionan un área de memoria, denominada montón o memoria libre , desde la cual se asignan objetos dinámicamente.

El siguiente ejemplo de código C ilustra cómo se asignan y referencian dinámicamente los objetos de estructura. La biblioteca estándar de C proporciona la función malloc()para asignar bloques de memoria desde el montón. Esta función recibe como parámetro el tamaño del objeto a asignar y devuelve un puntero a un bloque de memoria recién asignado, adecuado para almacenar el objeto, o un puntero nulo si la asignación falla.

// Elemento de inventario de piezas typedef struct { int id ; // Número de pieza char * name ; // Nombre de la pieza float cost ; // Costo } Item ;// Asigna e inicializa un nuevo objeto Item Item * makeItem ( const char * name ) { Item * item ;// Asigna un bloque de memoria para un nuevo objeto Item item = malloc ( sizeof ( * item )); if ( ! item ) { return NULL ; }// Inicializa los miembros del nuevo Item memset ( item , 0 , sizeof ( * item )); item -> id = -1 ; item -> name = NULL ; item -> cost = 0.0 ;// Guarda una copia del nombre en el nuevo Item item -> name = malloc ( strlen ( name ) + 1 ); if ( ! item -> name ) { free ( item ); return NULL ; } strcpy ( item -> name , name );// Devuelve el objeto Item recién creado return item ; }

El siguiente código ilustra cómo se liberan dinámicamente los objetos de memoria, es decir, cómo se devuelven al montón o al espacio de memoria libre. La biblioteca estándar de C proporciona la función free()para liberar un bloque de memoria previamente asignado y devolverlo al montón.

// Desasignar un objeto Item void destroyItem ( Item * item ) { // Comprobar si el puntero al objeto es nulo if ( ! item ) { return ; }// Libera la cadena de nombre guardada dentro del Item if ( item -> name ) { free ( item -> name ); item -> name = NULL ; }// Libera la memoria del objeto Item free ( item ); }

Hardware con mapeo de memoria

En algunas arquitecturas informáticas, los punteros pueden utilizarse para manipular directamente la memoria o los dispositivos mapeados en memoria.

Asignar direcciones a punteros es una herramienta invaluable al programar microcontroladores . A continuación se muestra un ejemplo simple que declara un puntero de tipo int y lo inicializa con una dirección hexadecimal , en este ejemplo la constante 0x7FFF :

int * dirección_hardware = ( int * ) 0x7FFF ;

A mediados de la década de 1980, acceder a las capacidades de vídeo de los PC mediante la BIOS era lento. Las aplicaciones que requerían un uso intensivo de la pantalla solían acceder directamente a la memoria de vídeo CGA convirtiendo la constante hexadecimal 0xB8000 en un puntero a una matriz de 80 valores enteros sin signo de 16 bits. Cada valor constaba de un código ASCII en el byte bajo y un color en el byte alto. Así, para colocar la letra ' A ' en la fila 5, columna 2, en blanco brillante sobre azul, se escribiría un código como el siguiente:

#define VID ((unsigned short (*)[80])0xB8000)vacío foo ( vacío ) { VID [ 4 ][ 1 ] = 0x1F00 | 'A' ; }

Uso en tablas de control

Las tablas de control que se utilizan para controlar el flujo del programa suelen hacer un uso extensivo de punteros. Estos punteros, generalmente integrados en una entrada de la tabla, pueden utilizarse, por ejemplo, para almacenar los puntos de entrada a las subrutinas que se ejecutarán, según ciertas condiciones definidas en la misma entrada. Sin embargo, los punteros también pueden ser simplemente índices a otras tablas separadas, pero asociadas, que contienen una matriz de direcciones reales o las direcciones mismas (dependiendo de las construcciones disponibles en el lenguaje de programación). Asimismo, pueden utilizarse para apuntar a entradas anteriores de la tabla (como en el procesamiento de bucles) o para avanzar y omitir algunas entradas (como en una instrucción switch o una salida anticipada de un bucle). Para este último caso, el puntero puede ser simplemente el número de la entrada de la tabla y transformarse en una dirección real mediante una operación aritmética sencilla.

Punteros tipados y conversión de tipos

En muchos lenguajes de programación, los punteros tienen la restricción adicional de que el objeto al que apuntan debe ser de un tipo específico . Por ejemplo, si se declara un puntero para que apunte a un entero , el lenguaje intentará impedir que el programador lo apunte a objetos que no sean enteros, como números de coma flotante , lo que elimina algunos errores.

Por ejemplo, en el siguiente código C:

int * dinero ; char * bolsas ;

moneysería un puntero a entero y bagssería un puntero a carácter.

Lo siguiente generaría una advertencia del compilador de "asignación desde un tipo de puntero incompatible" bajo GCC :

bolsas = dinero ;

porque moneyy bagsfueron declarados con diferentes tipos.

Para suprimir la advertencia del compilador, debe hacerse explícitamente la asignación mediante una conversión de tipo :

bolsas = ( char * ) dinero ;

que dice convertir el puntero entero de moneya un puntero de carácter y asignarlo a bags.

Un borrador de 2005 del estándar C requiere que la conversión de un puntero derivado de un tipo a otro tipo mantenga la corrección de alineación para ambos tipos (6.3.2.3 Punteros, párr. 7): [ 14 ]

char * external_buffer = "abcdef" ; int * internal_data ;internal_data = ( int * ) external_buffer ; // COMPORTAMIENTO INDEFINIDO si "el puntero resultante no está correctamente alineado"

En los lenguajes que permiten aritmética de punteros, la aritmética sobre punteros tiene en cuenta el tamaño del tipo. Por ejemplo, sumar un número entero a un puntero produce otro puntero que apunta a una dirección que es mayor en ese número multiplicado por el tamaño del tipo. Esto nos permite calcular fácilmente la dirección de los elementos de un array de un tipo dado, como se mostró en el ejemplo de arrays de C anterior. Cuando un puntero de un tipo se convierte a otro tipo de un tamaño diferente, el programador debe esperar que la aritmética de punteros se calcule de manera diferente. En C, por ejemplo, si el moneyarray comienza en 0x2000 y sizeof(int)es de 4 bytes mientras que sizeof(char)es de 1 byte, entonces money + 1apuntará a 0x2004 , pero apuntaría a 0x2001 . Otros riesgos de la conversión incluyen la pérdida de datos cuando se escriben datos "anchos" en ubicaciones "estrechas" ( por ejemplo, ), resultados inesperados al desplazar valores de bits y problemas de comparación, especialmente con valores con signo frente a valores sin signo.bags + 1bags[0] = 65537;

Si bien en general es imposible determinar en tiempo de compilación qué conversiones de tipo son seguras, algunos lenguajes almacenan información de tipo en tiempo de ejecución que puede utilizarse para confirmar que estas conversiones peligrosas son válidas en tiempo de ejecución. Otros lenguajes simplemente aceptan una aproximación conservadora de las conversiones seguras, o ninguna en absoluto.

Valor de los punteros

En C y C++, incluso si dos punteros se comparan como iguales, eso no significa que sean equivalentes. En estos lenguajes y en LLVM , la regla se interpreta como que "el hecho de que dos punteros apunten a la misma dirección no significa que sean iguales en el sentido de que se puedan usar indistintamente", la diferencia entre los punteros se denomina procedencia . [ 15 ] La conversión a un tipo entero, como uintptr_tes definida por la implementación, y la comparación que proporciona no ofrece más información sobre si los dos punteros son intercambiables. Además, la conversión posterior a bytes y aritmética confundirá a los optimizadores que intentan realizar un seguimiento del uso de punteros, un problema que aún se está dilucidando en la investigación académica. [ 16 ]

Hacer que los punteros sean más seguros

Dado que un puntero permite que un programa intente acceder a un objeto que puede no estar definido, los punteros pueden ser el origen de una variedad de errores de programación . Sin embargo, la utilidad de los punteros es tan grande que puede ser difícil realizar tareas de programación sin ellos. En consecuencia, muchos lenguajes han creado construcciones diseñadas para proporcionar algunas de las características útiles de los punteros sin algunos de sus inconvenientes , también conocidos a veces como riesgos de puntero . En este contexto, los punteros que direccionan directamente la memoria (como se usa en este artículo) se denominanpunteros sin procesar opunterosdesnudos, [ 17 ] en contraste conlos punteros inteligentesu otras variantes.

Un problema importante con los punteros es que, mientras se puedan manipular directamente como números, pueden apuntar a direcciones no utilizadas o a datos que se estén utilizando para otros fines. Muchos lenguajes, incluidos la mayoría de los lenguajes de programación funcional y los lenguajes de programación imperativos recientes como Java , reemplazan los punteros con un tipo de referencia más opaca, generalmente denominada simplemente referencia , que solo se puede usar para referirse a objetos y no se puede manipular como números, lo que evita este tipo de error. La indexación de matrices se trata como un caso especial.

Un puntero sin dirección asignada se denomina puntero salvaje . Cualquier intento de usar punteros no inicializados puede provocar un comportamiento inesperado, ya sea porque el valor inicial no es una dirección válida o porque su uso puede dañar otras partes del programa. El resultado suele ser un fallo de segmentación , una violación de memoria o un salto salvaje (si se usa como puntero a función o dirección de salto).

En sistemas con asignación explícita de memoria, es posible crear un puntero colgante al liberar la región de memoria a la que apunta. Este tipo de puntero es peligroso y sutil, ya que una región de memoria liberada puede contener los mismos datos que antes de liberarse, pero puede ser reasignada y sobrescrita posteriormente por código no relacionado, sin que el código anterior lo sepa. Los lenguajes con recolección de basura evitan este tipo de error, ya que la liberación se realiza automáticamente cuando no hay más referencias en el ámbito.

En lenguajes que abstraen los punteros y la aritmética de punteros, como Java , se pueden usar iteradores , que se consideran una forma más segura de iterar sobre un segmento de memoria o una colección sin usar el acceso directo a punteros. Muchos lenguajes, incluso aquellos que usan punteros, como C++, C# y Rust, tienen iteradores para colecciones.

import java.util.Iterator ; import java.util.List ;List < String > fruits = List . of ( "Apple" , "Banana" , "Cherry" , "Date" ); Iterator < String > iterator = fruits . iterator ();// Usa el iterador para recorrer la lista while ( iterador . hasNext ()) { // Obtén el siguiente elemento String fruit = iterador . next (); System . out . println ( fruit ); }

En C++, el iterador puede sobrecargarse operator++, de forma similar a la sintaxis tradicional de incrementar un puntero en C. Un iterador definido a medida tradicionalmente sobrecarga tres operadores:

  • operator==/ operator!=(comparación de igualdad)
  • operator++(incremento)
  • operator*(desreferencia)
importar std ;usando std :: vector ;// Las colecciones como vector definen un tipo iterador, // con los métodos begin() y end() vector < int > v { 1 , 2 , 3 , 4 , 5 }; // 'it' es de tipo vector<int>::iterator // Itera a través de la colección de forma similar a la aritmética de punteros for ( auto it = v . begin (); it != v . end (); ++ it ) { std :: print ( "{}" , * it ); }

Algunos lenguajes, como C++ , admiten punteros inteligentes , que utilizan una forma sencilla de conteo de referencias para ayudar a controlar la asignación de memoria dinámica, además de funcionar como referencia. En ausencia de ciclos de referencia, donde un objeto se referencia a sí mismo indirectamente a través de una secuencia de punteros inteligentes, estos eliminan la posibilidad de punteros colgantes y fugas de memoria. Las cadenas de Delphi admiten el conteo de referencias de forma nativa.

El lenguaje de programación Rust introduce un verificador de préstamos , tiempos de vida de punteros y una optimización basada en tipos de opciones para punteros nulos para eliminar errores de punteros, sin recurrir a la recolección de basura .

Tipos especiales de punteros

Tipos definidos por valor

Puntero nulo

Un puntero nulo tiene un valor reservado para indicar que el puntero no se refiere a un objeto válido. Los punteros nulos se utilizan habitualmente para representar condiciones como el final de una lista de longitud desconocida o el fallo al realizar alguna acción; este uso de los punteros nulos se puede comparar con los tipos anulables y con el valor Nothing en un tipo de opción .

Puntero colgante

Un puntero colgante es aquel que no apunta a un objeto válido y, por lo tanto, puede provocar que un programa falle o se comporte de forma anómala. En los lenguajes de programación Pascal o C , los punteros que no se inicializan específicamente pueden apuntar a direcciones de memoria impredecibles.

El siguiente código de ejemplo muestra un puntero colgante:

int func ( void ) { char * p1 = malloc ( sizeof ( char )); // Valor (indefinido) de algún lugar en el montón char * p2 ; // Puntero colgante (no inicializado) * p1 = 'a' ; // Esto está bien, suponiendo que malloc() no haya devuelto NULL. * p2 = 'b' ; // Esto provoca un comportamiento indefinido }

Aquí, p2puede apuntar a cualquier lugar de la memoria, por lo que realizar la asignación *p2 = 'b';puede corromper un área desconocida de la memoria o provocar un fallo de segmentación .

Rama silvestre

Cuando se utiliza un puntero como dirección de entrada a un programa o inicio de una función que no devuelve ningún valor y que, además, no está inicializado o está dañado, si se realiza una llamada o un salto a dicha dirección, se dice que se ha producido una " rama salvaje ". En otras palabras, una rama salvaje es un puntero a función que queda inactivo (colgante).

Las consecuencias suelen ser impredecibles y el error puede manifestarse de diversas maneras, dependiendo de si el puntero es una dirección válida y de si existe (casualmente) una instrucción válida (código de operación) en esa dirección. La detección de una bifurcación salvaje puede ser uno de los ejercicios de depuración más difíciles y frustrantes, ya que gran parte de la evidencia puede haberse destruido previamente o por la ejecución de una o más instrucciones inapropiadas en la ubicación de la bifurcación. Si está disponible, un simulador de conjunto de instrucciones generalmente no solo puede detectar una bifurcación salvaje antes de que tenga efecto, sino que también proporciona un registro completo o parcial de su historial.

Tipos definidos por estructura

Puntero autorreferencial

Un puntero autorrelativo es un puntero cuyo valor se interpreta como un desplazamiento desde la dirección del propio puntero; por lo tanto, si una estructura de datos tiene un miembro de puntero autorrelativo que apunta a alguna parte de la propia estructura de datos, entonces la estructura de datos puede reubicarse en la memoria sin tener que actualizar el valor del puntero autorrelativo. [ 18 ]

La patente citada también utiliza el término puntero autorreferencial para referirse a lo mismo. Sin embargo, el significado de este término se ha utilizado de otras maneras:

  • significa un desplazamiento desde la dirección de una estructura en lugar de desde la dirección del puntero en sí;
  • significa un puntero que contiene su propia dirección, lo cual puede ser útil para reconstruir en cualquier región arbitraria de la memoria una colección de estructuras de datos que apuntan entre sí. [ 19 ]

Puntero basado

Un puntero base es un puntero cuyo valor es un desplazamiento con respecto al valor de otro puntero. Esto se puede utilizar para almacenar y cargar bloques de datos, asignando la dirección del inicio del bloque al puntero base. [ 20 ]

Tipos definidos por uso o tipo de datos

Indirección múltiple

En algunos lenguajes, un puntero puede hacer referencia a otro puntero, lo que requiere múltiples operaciones de desreferenciación para acceder al valor original. Si bien cada nivel de indirección puede aumentar el rendimiento, a veces es necesario para garantizar el correcto funcionamiento de estructuras de datos complejas . Por ejemplo, en C, es habitual definir una lista enlazada mediante un elemento que contiene un puntero al siguiente elemento de la lista:

typedef struct LinkedList { int value ; struct LinkedList * next ; } LinkedList ;Lista enlazada * cabeza = NULL ;

Esta implementación utiliza un puntero al primer elemento de la lista como sustituto de la lista completa. Si se agrega un nuevo valor al principio de la lista, headdebe modificarse para que apunte al nuevo elemento. Dado que los argumentos de C siempre se pasan por valor, el uso de la doble indirección permite implementar la inserción correctamente y tiene el efecto secundario deseable de eliminar el código de casos especiales para gestionar las inserciones al principio de la lista.

// Dada una lista ordenada en *head, inserta el elemento item en la primera // ubicación donde todos los elementos anteriores tengan un valor menor o igual. void insert ( LinkedList ** head , LinkedList * item ) { // p apunta a un puntero a un elemento LinkedList ** p = head ; while ( * p && ( * p ) -> value < item -> value ) { p = & ( * p ) -> next ; } item -> next = * p ; * p = item ; }// El llamador hace esto: insertar ( & cabeza , elemento );

En este caso, si el valor de itemes menor que el de head, el de quien llama headse actualiza correctamente a la dirección del nuevo elemento.

Un ejemplo básico se encuentra en el argumento argv de la función principal en C (y C++) , que se da en el prototipo como char **argv(o char *argv[]); esto se debe a que la variable argven sí es un puntero a una matriz de cadenas (una matriz de matrices), por lo que *argves un puntero a la cadena 0 (por convención, el nombre del programa), y **argves el carácter 0 de la cadena 0.

Puntero a función

En algunos lenguajes de programación, un puntero puede hacer referencia a código ejecutable; es decir, puede apuntar a una función, método o procedimiento. Un puntero a función almacena la dirección de la función que se va a invocar. Si bien esta capacidad puede utilizarse para llamar a funciones de forma dinámica, suele ser una técnica predilecta de los creadores de virus y otro software malicioso.

// Función con dos parámetros enteros que devuelve un valor entero int sum ( int n1 , int n2 ) { return n1 + n2 ; }int main ( void ) { int a = 3 ; int b = 5 ; // Puntero a una función (int, int) -> int // y apunta a la función sum int ( * fp )( int , int ) = & sum ; int x = ( * fp )( a , b ); // Llama a la función sum con los argumentos a y b int y = sum ( a , b ); // Llama a la función sum con los argumentos a y b }

En C++ , se puede utilizar una expresión lambda ( función anónima ) en lugar de un puntero a función.

importar std ;usando Callback = void ( * )( int );void process ( int a , int b , Callback cb ) { callback ( a + b ); }void printResult ( int result ) { std :: println ( "Resultado: {}" , result ); }int main ( int argc , char * argv []) { // usando el puntero de función tradicional: process ( 1 , 2 , printResult ); // imprime: "Resultado: 3"// usando una lambda: process ( 3 , 4 , []( int result ) -> void { std :: println ( "Resultado: {}" ); }); // imprime: "Resultado: 7" }

puntero hacia atrás

En las listas doblemente enlazadas o estructuras de árbol , un puntero hacia atrás en un elemento apunta al elemento que hace referencia al elemento actual. Esto resulta útil para la navegación y la manipulación, aunque a costa de un mayor consumo de memoria.

Soporte en varios lenguajes de programación

Ada

Ada es un lenguaje fuertemente tipado donde todos los punteros tienen tipo y solo se permiten conversiones de tipo seguras. Todos los punteros se inicializan por defecto a null, y cualquier intento de acceder a datos a través de un nullpuntero provoca que se genere una excepción . Los punteros en Ada se denominan tipos de acceso . Ada  83 no permitía aritmética en tipos de acceso (aunque muchos proveedores de compiladores la proporcionaban como una característica no estándar), pero Ada  95 admite aritmética "segura" en tipos de acceso a través del paquete System.Storage_Elements.

BÁSICO

Varias versiones antiguas de BASIC para la plataforma Windows admitían la STRPTR()devolución de la dirección de una cadena y VARPTR()de una variable. Visual Basic 5 también admitía la OBJPTR()devolución de la dirección de una interfaz de objeto y el operador ADDRESSOF para devolver la dirección de una función. Todos estos tipos son enteros, pero sus valores son equivalentes a los de los punteros.

Sin embargo, las variantes más recientes de BASIC , como FreeBASIC o BlitzMax , cuentan con implementaciones de punteros exhaustivas. En FreeBASIC, las operaciones aritméticas con punteros ANY (equivalentes a los de C ) se tratan como si el puntero ANY tuviera un ancho de byte. Los punteros ANY no se pueden desreferenciar, como en C. Además, la conversión entre punteros ANY y de cualquier otro tipo no genera advertencias.void *

dim as integer f = 257 dim as any ptr g = @ f dim as integer ptr i = g assert ( * i = 257 ) assert ( ( g + 4 ) = ( @ f + 1 ) )

do

En C, los punteros son variables que almacenan direcciones y pueden ser nulos . Cada puntero tiene un tipo al que apunta, pero se puede convertir libremente entre tipos de puntero. Sin embargo, no es posible convertir entre un puntero a función y un puntero a objeto. Un tipo de puntero especial llamado "puntero void" permite apuntar a cualquier objeto (que no sea una función), pero está limitado por el hecho de que no se puede desreferenciar directamente. La dirección en sí misma a menudo se puede manipular directamente convirtiendo un puntero a y desde un tipo entero de tamaño suficiente, aunque los resultados son indefinidos; mientras que los estándares C anteriores no tenían un tipo entero que garantizara ser lo suficientemente grande, C99 especifica el uintptr_ttipo definido en <stdint.h>, pero una implementación no necesita proporcionarlo.

La aritmética de punteros , es decir, la capacidad de modificar la dirección de destino de un puntero con operaciones aritméticas (así como comparaciones de magnitud), está restringida por el estándar del lenguaje para permanecer dentro de los límites de un solo objeto de matriz o justo después de él, y de lo contrario invocará un comportamiento indefinido . Sumar o restar de un puntero lo mueve en un múltiplo del tamaño de su tipo de dato . Por ejemplo, sumar 1 a un puntero a valores enteros de 4 bytes incrementará la dirección de byte a la que apunta el puntero en 4. Esto tiene el efecto de incrementar el puntero para que apunte al siguiente elemento en una matriz contigua de enteros, que suele ser el resultado deseado. La aritmética de punteros no se puede realizar en voidpunteros porque el tipo void no tiene tamaño, y por lo tanto no se puede sumar a la dirección a la que apunta, aunque gcc y otros compiladores realizarán aritmética de bytes en void *como una extensión no estándar, tratándolo como si fuera char *.

La aritmética de punteros proporciona al programador una única forma de manejar diferentes tipos: sumar y restar el número de elementos requeridos en lugar del desplazamiento real en bytes. En particular, el estándar C declara explícitamente que la sintaxis a[n], que es el n -ésimo elemento del array , es equivalente a , que es el contenido del elemento apuntado por . Esto implica que, por ejemplo, y acceden al cuarto elemento del array .a*(a + n)a + na[3]3[a]a

Si bien es potente, la aritmética de punteros puede ser fuente de errores informáticos . Tiende a confundir a los programadores novatos , obligándolos a trabajar en contextos diferentes: una expresión puede ser aritmética ordinaria o aritmética de punteros, y a veces es fácil confundirlas. En respuesta a esto, muchos lenguajes de programación modernos de alto nivel no permiten el acceso directo a la memoria mediante direcciones.

El voidpuntero , o void *, es compatible en ANSI C como un tipo de puntero genérico. Un puntero a voidpuede almacenar la dirección de cualquier objeto (no función), [ a ] ​​y, se convierte implícitamente a cualquier otro tipo de puntero de objeto en la asignación, pero debe convertirse explícitamente si se desreferencia. Antes de ANSI C, K&R usaba char *para el propósito de "puntero independiente del tipo".

int x = 4 ; void * p1 = & x ; int * p2 = p1 ; // void * se convierte implícitamente a int * int a = * p2 ; int b = * ( int * ) p1 ; // al desreferenciar en línea, no hay conversión implícita

C++

C++ es totalmente compatible con punteros y conversión de tipos de C. Además, admite un nuevo grupo de operadores de conversión de tipos para detectar conversiones peligrosas no deseadas en tiempo de compilación. C++ también admite otra forma de referencia, bastante diferente de un puntero, llamada simplemente referencia o tipo de referencia .

Desde C++11 , la biblioteca estándar de C++ también proporciona punteros inteligentes , que pueden utilizarse en algunas situaciones como una alternativa más segura a los punteros primitivos de C.

  • std::unique_ptr(Representa la propiedad exclusiva de un recurso, no se puede copiar pero sí se puede mover)
  • std::shared_ptr(Representa la propiedad compartida de un recurso; el recurso solo se elimina una vez que se destruye o se restablece el último puntero compartido).
  • std::weak_ptr(no representa propiedad, puede apuntar a los recursos de un puntero compartido sin incrementar el contador de referencias)
  • std::hazard_pointer(puntero de un solo escritor y múltiples lectores que puede ser propiedad de como máximo un hilo en cualquier momento)

std::unique_ptrSustituye al puntero inteligente anterior std::auto_ptr , que quedó obsoleto en C++11 y se eliminó en C++17.

A diferencia de C, C++ no permite la conversión implícita a void*otros tipos de puntero, ni siquiera en asignaciones. Esta fue una decisión de diseño para evitar conversiones de tipos descuidadas e incluso no intencionadas, aunque la mayoría de los compiladores solo emiten advertencias, no errores, cuando encuentran otras conversiones.

int x = 4 ; void * p1 = & x ; int * p2 = p1 ; // esto falla: no hay conversión implícita desde void* int * p3 = reinterpret_cast < int *> ( p1 ); // conversión de C++

En C++, no existe void&(referencia a void) para complementar void*(puntero a void), porque las referencias se comportan como alias de las variables a las que apuntan, y nunca puede haber una variable cuyo tipo sea void.

Puntero a miembro

En C++ se pueden definir punteros a miembros no estáticos de una clase. Si una clase MyClasstiene un miembro, T aentonces &MyClass::aes un puntero al miembro ade tipo T MyClass::*. Este miembro puede ser un objeto o una función . [ 22 ] Se pueden usar en el lado derecho de los operadores .*y ->*para acceder al miembro correspondiente.

struct Integer { int value ; [[ nodiscard ]] int get () const noexcept { return value ; } };Entero s1 ; // construido por defecto, s1.valor = 0 Entero * ps = & s1 ;int Integer ::* ptr = & Integer :: value ; // puntero a Integer::value int ( Integer ::* fp )() const = & Integer :: get ; // puntero a Integer::gets1 . * ptr = 1 ; std :: println ( "{}" , ( s1 . * fp )()); // imprime 1 ps ->* ptr = 2 ; std :: println ( "{}" , ( ps ->* fp )()); // imprime 2

Las siguientes son declaraciones que involucran punteros a miembros:

clase X { // ... };clase Y { // ... };char X ::* a ; // puntero a miembro de char char X ::* b [ 5 ]; // matriz de punteros a miembros de char char * X ::* c ; // puntero a miembro a puntero a char(s) char X ::** d ; // puntero a puntero a miembro de char char ( * e )[ 5 ]; // puntero a miembro a matriz(s) de caracteres char X ::* f (); // función que devuelve un puntero a miembro de char char Y ::* X ::* g ; // puntero a miembro a puntero a miembro a puntero a char(s) char X ::* X ::* h ; // puntero a miembro a puntero a miembro a puntero a char(s) char ( X ::* i ())[ 5 ]; // función que devuelve un puntero a miembro a un array de caracteres char ( X ::* j )() // función de puntero a miembro que devuelve un carácter char ( X ::* k [ 5 ])(); // un array de punteros a funciones miembro que devuelven un carácter

Los ()y []tienen mayor prioridad que *. [ 23 ]

DO#

En el lenguaje de programación C# , los punteros se admiten marcando los bloques de código que los incluyen con la unsafepalabra clave `pointer`, o a través del System.Runtime.CompilerServicesespacio de nombres para el acceso a punteros. La sintaxis es esencialmente la misma que en C++, y la dirección a la que apunta puede ser memoria administrada o no administrada . Sin embargo, los punteros a memoria administrada (cualquier puntero a un objeto administrado) deben declararse con la fixedpalabra clave `pointer`, lo que impide que el recolector de basura mueva el objeto apuntado como parte de la administración de memoria mientras el puntero está dentro del ámbito, manteniendo así la validez de la dirección del puntero.

Sin embargo, una excepción a esto es el uso de la IntPtrestructura, que es un equivalente administrado en memoria a int*, y no requiere la unsafepalabra clave ni el CompilerServicesensamblado. Este tipo se devuelve a menudo al usar métodos de System.Runtime.InteropServices, por ejemplo:

using System ; using System.Runtime.InteropServices ;// Obtener 16 bytes de memoria de la memoria no administrada del proceso IntPtr pointer = Marshal . AllocHGlobal ( 16 );// Hacer algo con la memoria asignada// Libera la memoria asignada Marshal.FreeHGlobal ( pointer );

El marco .NET incluye muchas clases y métodos en los espacios de nombres Systemy System.Runtime.InteropServices(como la Marshalclase) que convierten tipos .NET (por ejemplo, System.String) a y desde muchos tipos y punteros no administradosLPWSTR (por ejemplo, o void*) para permitir la comunicación con código no administrado . La mayoría de estos métodos tienen los mismos requisitos de permisos de seguridad que el código no administrado, ya que pueden afectar ubicaciones arbitrarias en la memoria.

C# permite el uso de matrices asignadas en la pila en código seguro mediante System.Span. [ 24 ]

espacio de nombres Wikipedia.Ejemplos ;usando el sistema ;public class Example { static void Main ( string [] args ) { int num = 1024 ; unsafe { // convierte un int en bytes creando un puntero a byte byte * p = ( byte * ) & number ; Console . Write ( "Los 4 bytes del entero son: " ); for ( int i = 0 ; i < sizeof ( int ); ++ i ) { Console . Write ( " {0:X2}" , * p ); ++ p ; } Console . WriteLine (); }// Los arreglos asignados en la pila se pueden hacer mediante punteros o Span<T> unsafe { int * numbers = stackalloc int [ 5 ]; } Span < int > numbers = stackalloc int [ 5 ]; } }

C#, al igual que C y C++, también tiene un void*tipo (puntero void), pero no es muy recomendable. [ 25 ]

A diferencia de C y C++, el compilador no acepta asteriscos alineados a la derecha. Por ejemplo:

int * a , b ; int * a , * b ; // ilegal en C#

En C y C++, esto declararía acomo int*y bcomo int, pero en C# esto declara ambas variables como int*. [ 25 ]

COBOL

El lenguaje de programación COBOL admite punteros a variables. Los objetos de datos primitivos o de grupo (registro) declarados dentro de LINKAGE SECTIONun programa se basan inherentemente en punteros, donde la única memoria asignada dentro del programa es el espacio para la dirección del elemento de datos (normalmente una sola palabra de memoria). En el código fuente del programa, estos elementos de datos se utilizan como cualquier otra WORKING-STORAGEvariable, pero su contenido se accede implícitamente de forma indirecta a través de sus LINKAGEpunteros.

El espacio de memoria para cada objeto de datos al que apunta se asigna normalmente de forma dinámica mediante instrucciones externas CALLo a través de construcciones de lenguaje extendidas integradas, como las instrucciones EXEC CICSo EXEC SQL.

Las versiones extendidas de COBOL también proporcionan variables de puntero declaradas con USAGEISPOINTERcláusulas. Los valores de dichas variables de puntero se establecen y modifican mediante SETsentencias SETADDRESS.

Algunas versiones extendidas de COBOL también proporcionan PROCEDURE-POINTERvariables, que son capaces de almacenar las direcciones del código ejecutable .

PL/I

El lenguaje PL/I proporciona soporte completo para punteros a todos los tipos de datos (incluidos punteros a estructuras), recursión , multitarea , manejo de cadenas y funciones integradas extensas . PL/I fue un gran salto adelante en comparación con los lenguajes de programación de su época. Los punteros PL/I no tienen tipo, por lo que no se requiere conversión para la desreferenciación o asignación de punteros. La sintaxis de declaración para un puntero es , que declara un puntero llamado "xxx". Los punteros se utilizan con variables. Una variable base se puede declarar con un localizador predeterminado ( o sin ( ), donde xxx es una variable base, que puede ser una variable de elemento, una estructura o una matriz, y ppp es el puntero predeterminado). Dicha variable se puede direccionar sin una referencia de puntero explícita ( , o se puede direccionar con una referencia explícita al localizador predeterminado (ppp), o a cualquier otro puntero ( ).DECLARE xxx POINTER;BASEDDECLARE xxx BASED(ppp);DECLARE xxx BASED;xxx=1;qqq->xxx=1;

La aritmética de punteros no forma parte explícitamente del estándar PL/I, pero la UNSPECfunción integrada y la pseudovariable permiten realizarla, y muchos compiladores admiten expresiones de la forma ptr = ptr±expression. IBM PL/I también dispone de la función integrada PTRADDpara realizar la aritmética. La aritmética de punteros siempre se realiza en bytes.

Los compiladores PL/I de IBM Enterprise tienen una nueva forma de puntero tipado llamada HANDLE.

D

El lenguaje de programación D es un derivado de C y C++ que admite completamente los punteros y la conversión de tipos de C. Admite aritmética de punteros y también tiene segmentos de puntero. Un puntero en D puede decaer void*implícitamente. [ 26 ]

En D, es posible usar refpara una referencia, que desreferencia automáticamente, no permite la aritmética de punteros y no permite escapar una función (es decir, a través de return).

Una porción de puntero puede verse como un puntero con una longitud.

import std.stdio ;void main ( ) { int [] arr = [ 10 , 20 , 30 , 40 , 50 ] ; int * ptr = & arr [ 0 ]; int [] slice = ptr [ 0..3 ]; // Divide el array desde el índice 0 hasta el 3 (exclusivo) writeln ( "Slice: " , slice ); // Salida: Slice: [10, 20, 30] }

Eiffel

El lenguaje orientado a objetos Eiffel emplea semántica de valor y referencia sin aritmética de punteros. Sin embargo, se proporcionan clases de punteros que ofrecen aritmética de punteros, conversión de tipos, gestión explícita de memoria, interfaz con software que no es Eiffel y otras características.

Fortran

Fortran-90 introdujo la capacidad de punteros fuertemente tipados. Los punteros de Fortran contienen más que una simple dirección de memoria. También encapsulan los límites inferior y superior de las dimensiones de los arreglos, los pasos (por ejemplo, para admitir secciones de arreglos arbitrarias) y otros metadatos. Se utiliza un operador de asociación para asociar un puntero a una variable que tiene un atributo. La instrucción de Fortran-90 también se puede usar para asociar un puntero a un bloque de memoria. Por ejemplo, el siguiente código podría usarse para definir y crear una estructura de lista enlazada:=>POINTERTARGETALLOCATE

tipo real_list_t real :: sample_data ( 100 ) tipo ( real_list_t ), puntero :: next => null () fin tipotipo ( lista_real_t ), destino :: mi_lista_real tipo ( lista_real_t ), puntero :: lista_real_tempreal_list_temp => my_real_list do  read ( 1 , iostat = ioerr ) real_list_temp % sample_data if ( ioerr /= 0 ) exit  allocate ( real_list_temp % next ) real_list_temp => real_list_temp % next end do

Fortran-2003 añade compatibilidad con punteros a procedimientos. Además, como parte de la función de interoperabilidad con C , Fortran-2003 admite funciones intrínsecas para convertir punteros de estilo C en punteros de Fortran y viceversa.

Ir

Go tiene punteros, declarados de forma similar a los punteros de C/C++ pero con el asterisco primero. Por ejemplo, un puntero a Tse escribe *T. A diferencia de C, Go tiene recolección de basura y no permite aritmética de punteros. Los tipos de referencia, como en C++, no existen. Algunos tipos integrados, como mapas y canales, están encapsulados (es decir, internamente son punteros a estructuras mutables) y se inicializan usando la makefunción. En un enfoque para unificar la sintaxis entre punteros y no punteros, ->se ha eliminado el operador de flecha ( ): el operador punto en un puntero se refiere al campo o método del objeto desreferenciado. Sin embargo, esto solo funciona con un nivel de indirección.

Java

En Java no existe una representación explícita de punteros . En su lugar, las estructuras de datos más complejas, como objetos y matrices, se implementan mediante referencias . El lenguaje no proporciona operadores explícitos para la manipulación de punteros. Sin embargo, es posible que el código intente desreferenciar una referencia nula (puntero nulo), lo que genera una excepción java.lang.NullPointerException . El espacio ocupado por los objetos de memoria sin referencias se recupera automáticamente mediante la recolección de basura en tiempo de ejecución. [ 27 ]

Java proporciona las clases java.lang.ref.WeakReferencey java.lang.ref.PhantomReference, que implementan respectivamente referencias débiles y referencias fantasma .

Módulo-2

Los punteros se implementan de forma muy similar a Pascal, al igual que VARlos parámetros en las llamadas a procedimientos. Modula-2 es incluso más fuertemente tipado que Pascal, con menos maneras de eludir el sistema de tipos. Algunas variantes de Modula-2 (como Modula-3 ) incluyen recolección de basura.

Oberón

Al igual que en Modula-2, se pueden usar punteros. Aún existen menos maneras de eludir el sistema de tipos, por lo que Oberon y sus variantes siguen siendo más seguros con respecto a los punteros que Modula-2 o sus variantes. Como en Modula-3 , la recolección de basura forma parte de la especificación del lenguaje.

Pascal

A diferencia de muchos lenguajes que utilizan punteros, el estándar ISO Pascal solo permite que los punteros hagan referencia a variables creadas dinámicamente que sean anónimas y no permite que hagan referencia a variables estáticas o locales estándar. [ 28 ] No tiene aritmética de punteros. Los punteros también deben tener un tipo asociado y un puntero a un tipo no es compatible con un puntero a otro tipo (por ejemplo, un puntero a un char no es compatible con un puntero a un integer). Esto ayuda a eliminar los problemas de seguridad de tipos inherentes a otras implementaciones de punteros, en particular las utilizadas para PL/I o C. También elimina algunos riesgos causados ​​por punteros colgantes , pero la capacidad de liberar dinámicamente el espacio referenciado mediante el disposeprocedimiento estándar (que tiene el mismo efecto que la freefunción de biblioteca que se encuentra en C ) significa que el riesgo de punteros colgantes no se ha eliminado. [ 29 ]

Sin embargo, en algunas implementaciones de compiladores Pascal (o derivados) comerciales y de código abierto —como Free Pascal , [ 30 ] Turbo Pascal o Object Pascal en Embarcadero Delphi— se permite que un puntero haga referencia a variables estáticas o locales estándar y se puede convertir de un tipo de puntero a otro. Además, la aritmética de punteros no tiene restricciones: sumar o restar a un puntero lo mueve en esa cantidad de bytes en cualquier dirección, pero usar los Incprocedimientos Decestándar con él mueve el puntero en el tamaño del tipo de datos al que se declara que apunta. También se proporciona un puntero sin tipo bajo el nombre Pointer, que es compatible con otros tipos de puntero.

Punteros de Pascal

Los punteros se declaran mediante el operador de desreferenciación ^. Cuando se utiliza en la definición de una variable, el puntero es una referencia a un tipo, que puede ser intrínseco (definido por el compilador del lenguaje) o declarado explícitamente, ya sea en otra unidad, en la unidad actual o en el programa principal.

Una declaración de puntero tiene este aspecto (Pascal no distingue entre mayúsculas y minúsculas , por lo que los tipos y las variables pueden usar mayúsculas o minúsculas):

Tipomeses = ( ene , feb , mar , abr , may , jun , jul , ago , sep , oct , nov , dic ) ;calptr = ^ Tipo_Calendario ;Tipo_de_calendario = registroAnterior , Siguiente : CalPtr ;Mes : Meses ;día : byte ;año : entero ;evento : cadena ;fin ;varEso : ^ entero ;Siguiente calendario ,calendario : ^ calptr ;comenzarnuevo ( calendario )calendario ^. anterior := nil ;calendario ^. siguiente := nil ;Calendario ^. Mes := Sep ;Calendario ^. Día := 11 ;Calendario ^. Año := 2001 ;nuevo ( que ) ;Eso ^ := 1 ;Nuevo ( nextCalendar ) ;nextCalendar ^. prev := Calendar ;nextCalendar ^. Siguiente := NIL ;Nuevo ( Calendario ) ;...Desechar ( eso ) ;Desechar ( calendario ) ;Eliminar ( nextCalendar ) ;fin ;

La línea 2 muestra la creación de un nuevo tipo `months` y crea constantes con nombre para los meses del año. La línea 3 define un puntero a un tipo de registro. Las líneas 4-10 definen ese registro. Las líneas 12-15 definen las variables de puntero; la línea 13 define un puntero a un escalar y las líneas 14-15 crean una lista enlazada. La línea 18 asigna memoria a través de la nueva función y almacena la dirección de esa memoria en la variable ` calendar` . Las líneas 19-23 inicializan una lista enlazada. La línea 26 muestra la asignación a la memoria a la que apunta el puntero `that` . Las líneas 28 a 31 muestran la creación de un nuevo enlace en la lista enlazada. Las líneas 33-35 liberan la memoria asignada a esos punteros.

Perl

El lenguaje de programación Perl admite punteros, aunque raramente se utilizan, en forma de las funciones `pack` y `unpack`. Estas están destinadas únicamente a interacciones sencillas con bibliotecas del sistema operativo compiladas. En todos los demás casos, Perl utiliza referencias , que son tipadas y no permiten ningún tipo de aritmética de punteros. Se utilizan para construir estructuras de datos complejas. [ 31 ]

Óxido

Rust utiliza referencias, que son similares a los punteros pero garantizan que apuntan a un valor válido. Trabajar con punteros sin formato es menos común, generalmente para la programación de sistemas de bajo nivel. Los punteros sin formato solo se pueden desreferenciar dentro de un unsafebloque . [ 32 ] [ 33 ] [ 34 ] Las operaciones con punteros sin formato se encuentran en std::ptr.

En Rust, no existe una constante de puntero nulo. En su lugar, se proporciona mediante std::ptr::null().

Rust también tiene varios tipos de punteros inteligentes:

  • std::boxed::Box: equivalente a C++ std::unique_ptr[ 35 ]
  • std::rc::Rc: equivalente a un puntero compartido de un solo hilo con conteo de referencias
  • std::sync::Arc: equivalente a un puntero compartido seguro para subprocesos con conteo de referencias atómico
  • std::rc::Weak: equivalente a C++ std::weak_ptr[ 36 ]

Un puntero Tse escribe como *const To *mut Ten Rust. El siguiente ejemplo muestra punteros sin formato en Rust:

fn main () { let mut num : i32 = 42 ;let r1 = & num as * const i32 ; let r2 = & mut num as * mut i32 ;unsafe { println! ( "r1 apunta a: {}" , * r1 ); println! ( "r2 apunta a: {}" , * r2 );* r2 = 100 ; println! ( "num ahora es: {}" , num ); } }

Véase también

Notas

  1. Algunos compiladores permiten almacenar las direcciones de las funciones en punteros void. El estándar C indica que dichas conversiones son "extensiones comunes". Esto es requerido por lafunción POSIXdlsym . [ 21 ]

Referencias

  1. Knuth, Donald (1974). "Programación estructurada con instrucciones go to" (PDF) . Computing Surveys . 6 (5): 261– 301. CiteSeerX 10.1.1.103.6084 . doi : 10.1145/356635.356640 . S2CID 207630080. Archivado del original (PDF) el 24 de agosto de 2009.  
  2. Reilly, Edwin D. (2003). Hitos en Ciencias de la Computación y Tecnologías de la Información . Greenwood Publishing Group. pág . 204. ISBN  9781573565219. Consultado el 13 de abril de 2018. Harold Lawson pointer.
  3. "Lista de premios de la IEEE Computer Society" . Awards.computer.org. Archivado del original el 22 de marzo de 2011. Consultado el 13 de abril de 2018 .
  4. ISO/IEC 9899 , ​​cláusula 6.7.5.1, párrafo 1.
  5. 1 2 Ritchie, Dennis M. (1993-03-01). "El desarrollo del lenguaje C" . SIGPLAN Notices . 28 (3). ACM : 201–208 . doi : 10.1145/155360.155580 .
  6. Bjarne Stroustrup (26 de febrero de 2022). "Stroustrup: Preguntas frecuentes sobre técnicas y estilos de C++" . stroustrup.com . Bjarne Stroustrup.
  7. Stroustrup, Bjarne (2013). «Punteros, arreglos y referencias». El lenguaje de programación C++ (4.ª ed.). Addison-Wesley. pág. 172. ISBN   978-0-321-56384-2.
  8. ISO/IEC 9899 , ​​cláusula 6.7.8, párrafo 10.
  9. ISO/IEC 9899 , ​​cláusula 7.17, párrafo 3: NULL... que se expande a una constante de puntero nulo definida por la implementación...
  10. Gustedt, Jens; Meneide, JeanHeyd (22 de julio de 2022). "Introducción de la constante nullptr" . Recuperado el 13 de julio de 2025 .
  11. ISO/IEC 9899 , ​​cláusula 6.5.3.2, párrafo 4, nota al pie 87: Si se ha asignado un valor no válido al puntero, el comportamiento del *operador unario es indefinido... Entre los valores no válidos para la desreferenciación de un puntero mediante el *operador unario se encuentra un puntero nulo...
  12. Plauger, PJ ; Brodie , Jim (1992). ANSI and ISO Standard C Programmer's Reference . Redmond, WA: Microsoft Press. p. 108. ISBN  978-1-55615-359-4.
  13. " § 6.3.2.1: Valores L, matrices y designadores de funciones". WG14-N3096: Borrador para C23: ISO/IEC 9899:2023 (PDF) . ISO / IEC . 1 de abril de 2023. pág. 48. Archivado (PDF) del original el 2 de abril de 2023. 
  14. WG14 N1124 , C – Normas aprobadas: ISO/IEC 9899 – Lenguajes de programación – C , 2005-05-06.
  15. Jung, Ralf. "Los punteros son complicados II, o: Necesitamos mejores especificaciones de lenguaje" .
  16. Jung, Ralf. "Los punteros son complicados, o: ¿Qué hay en un byte?" .
  17. "Código fuente del encabezado de la JVM" . GitHub .
  18. ↑ Patente estadounidense 6625718 , Steiner, Robert C. (Broomfield, CO), "Punteros que son relativos a sus propias ubicaciones actuales", emitida el 23 de septiembre de 2003, asignada a Avaya Technology Corp. (Basking Ridge, NJ) 
  19. ↑ Patente estadounidense 6115721 , Nagy, Michael (Tampa, FL), "Sistema y método para guardar y restaurar bases de datos usando punteros propios", emitida el 5 de septiembre de 2000, asignada a IBM (Armonk, NY) 
  20. "Punteros basados ​​(C++)" . Msdn.microsoft.com . Consultado el 13 de abril de 2018 .
  21. "Conversión entre punteros a funciones y objetos" . CWG (195) . Recuperado el 15 de febrero de 2024 a través de cplusplus.github.io.
  22. "Punteros a funciones miembro" . Super-FAQ de C++ . Fundación estándar de C++ . Consultado el 26/11/2022 .
  23. ^ Bilting, Ulf; Skansholm, enero (2000). Vägen hasta C [ El camino hacia C ] (en sueco) (3ª ed.). Literatura estudiantil. pag. 169.ISBN   91-44-01468-6.
  24. "expresión stackalloc" . learn.microsoft.com . Microsoft Learn. 10 de julio de 2024.
  25. 1 2 "Código inseguro, tipos de punteros y punteros a funciones" . learn.microsoft.com . Microsoft Learn. 6 de febrero de 2025.
  26. "Tipos - Lenguaje de programación D" . dlang.org . Documentación del lenguaje D. Consultado el 2 de noviembre de 2025 .
  27. Nick Parlante,, Biblioteca de Educación en Ciencias de la Computación de Stanford , págs. 9–10 (2000).
  28. ISO 7185 Estándar Pascal (copia no oficial), sección 6.4.4 Tipos de punteros Archivado el 24/04/2017 en Wayback Machine y posteriores.
  29. J. Welsh, WJ Sneeringer y CAR Hoare, "Ambiguidades e inseguridades en Pascal", Software: Practice and Experience 7 , págs. 685–696 (1977)
  30. Guía de referencia del lenguaje Free Pascal, sección 3.4 Punteros
  31. Datos de contacto. "// Cómo hacer referencias (Referencias Perl y estructuras de datos anidadas)" . Perldoc.perl.org . Consultado el 13 de abril de 2018 .
  32. Klabnik, Steve; Nichols, Carol (2023). «Referencias y préstamos» . El lenguaje de programación Rust (2.ª ed.). No Starch Press. ISBN  978-1-7185-0310-6OCLC 1363816350 .​ 
  33. Klabnik, Steve; Nichols, Carol (2023). "Unsafe Rust" . El lenguaje de programación Rust (2.ª ed.). No Starch Press. ISBN  978-1-7185-0310-6OCLC 1363816350 .​ 
  34. "puntero - Rust" . doc.rust-lang.org . Consultado el 15/12/2025 .
  35. "Box en std::boxed" . doc.rust-lang.org . 4 de agosto de 2025.
  36. "Débil en std::rc" . doc.rust-lang.org . 4 de agosto de 2025.
  • Documento sobre el procesamiento de listas PL/I del número de junio de 1967 de CACM.
  • cdecl.org Una herramienta para convertir declaraciones de punteros a lenguaje natural.
  • En IQ.com encontrarás una guía para principiantes que describe consejos en un lenguaje sencillo.
  • Punteros y memoria. Introducción a los punteros – Biblioteca de Educación en Ciencias de la Computación de Stanford.
  • Punteros en la programación en C. Archivado el 9 de junio de 2019 en Wayback Machine. Un modelo visual para programadores principiantes en C.
  • 0pointer.de Una lista concisa de códigos fuente de longitud mínima que desreferencian un puntero nulo en varios lenguajes de programación diferentes.
  • "El libro de C" – que contiene ejemplos de punteros en ANSI C
  • Comité Técnico Conjunto ISO/IEC JTC 1, Subcomité SC 22, Grupo de Trabajo WG 14 (08-09-2007). Norma Internacional ISO/IEC 9899 (PDF) .{{cite book}}: CS1 maint: nombres múltiples: lista de autores ( enlace ) CS1 maint: nombres numéricos: lista de autores ( enlace ) Borrador del comité.