Este artículo necesita citas adicionales para su verificación . ( abril de 2018 ) |
Considero que las declaraciones de asignación y las variables de puntero están entre los "tesoros más valiosos" de la ciencia informática.
Donald Knuth , Programación estructurada, con instrucciones go to [1]
En informática , un puntero es un objeto presente en muchos lenguajes de programación que almacena una dirección de memoria . Esta puede ser la de otro valor ubicado en la memoria de la computadora o, en algunos casos, la de un hardware de computadora asignado a la 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. Como analogía, un número de página en el índice de un libro podría considerarse un puntero a la página correspondiente; la desreferenciación de dicho puntero se haría pasando a la página con el número de página dado y leyendo el texto que se encuentra en esa página. El formato y el contenido reales de una variable de puntero dependen de la arquitectura de la computadora subyacente .
El uso de punteros mejora significativamente el rendimiento de las operaciones repetitivas, como recorrer estructuras de datos iterables (por ejemplo, cadenas , tablas de búsqueda , tablas de control y estructuras de árbol ). En particular, suele ser mucho más económico en tiempo y espacio copiar y desreferenciar punteros que copiar y acceder a los datos a los que apuntan los punteros.
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 a 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 utilizando 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 de manera más apropiada a estructuras de datos cuya interfaz permite explícitamente manipular el puntero (aritméticamente a través dearitmética de punteros ) como una dirección de memoria, a diferencia de unacookie mágicaocapacidadque no lo permite.[ cita requerida ]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 último 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 es una dirección de memoria válida podría hacer que un programa sebloquee(o contenga datos no válidos). Para aliviar este problema potencial, como una cuestión deseguridad de tipos, los punteros se consideran un tipo separado parametrizado por el tipo de datos al que apuntan, incluso si la representación subyacente es un entero. También se pueden tomar otras medidas (comola validaciónyla comprobación de límites), para verificar que la variable de puntero contiene un valor que es una dirección de memoria válida y está dentro del rango numérico que el procesador es capaz de abordar.
En 1955, la científica informática soviética ucraniana Kateryna Yushchenko creó el lenguaje de programación Address que hizo posible el direccionamiento indirecto y las direcciones del más alto rango, análogas a los punteros. Este lenguaje fue ampliamente utilizado en las computadoras de la Unión Soviética. Sin embargo, era desconocido fuera de la Unión Soviética y generalmente se le atribuye a Harold Lawson la invención, en 1964, del puntero. [2] En 2000, Lawson recibió el Premio Pionero de la Computación del IEEE "por 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 seminal sobre los conceptos apareció en la edición 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 la System Development Corporation .
En informática , un puntero es un tipo de referencia .
Un primitivo de datos (o simplemente primitivo ) es cualquier dato que se puede leer o escribir en la memoria de una computadora usando un acceso a la memoria (por ejemplo, tanto un byte como una palabra son primitivos).
Un agregado de datos (o simplemente agregado ) es un grupo de primitivas que son lógicamente contiguas en la memoria y que se consideran colectivamente como un dato (por ejemplo, un agregado podría ser 3 bytes lógicamente contiguos, cuyos valores representan las 3 coordenadas de un punto en el espacio). Cuando un agregado está compuesto completamente del mismo tipo de primitiva, el agregado puede llamarse matriz ; en cierto sentido, una primitiva de palabra de varios bytes es una matriz de bytes, y algunos programas usan palabras de esta manera.
Un puntero es un concepto de programación utilizado en informática para hacer referencia o señalar una ubicación de memoria que almacena un valor o un objeto. Es esencialmente una variable que almacena la dirección de memoria de otra variable o estructura de datos en lugar de almacenar los datos en sí.
Los punteros se utilizan habitualmente en lenguajes de programación que admiten la manipulación directa de la memoria, como C y C++. Permiten a los programadores trabajar con la memoria directamente, lo que permite una gestión eficiente de la memoria y estructuras de datos más complejas. Al utilizar punteros, se puede acceder a los datos ubicados en la memoria y modificarlos, pasar datos de manera eficiente entre funciones y crear estructuras de datos dinámicas como listas enlazadas, árboles y gráficos.
En términos más simples, puedes pensar en un puntero como una flecha que apunta a un punto específico en la memoria de una computadora, permitiéndote interactuar con los datos almacenados en esa ubicación.
Un puntero de memoria (o simplemente puntero ) es un 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 del dato.
En términos más generales, un puntero es un tipo de referencia y se dice que un puntero hace referencia a un dato almacenado en algún lugar de la memoria ; para obtener ese dato se debe desreferenciar el puntero . La característica que separa a los punteros de otros tipos de referencia es que el valor de un puntero debe interpretarse como una dirección de memoria, que es un concepto de nivel bastante bajo.
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. Debido a que la indirección es un aspecto fundamental de los algoritmos, los punteros se expresan a menudo como un tipo de datos fundamental en los lenguajes de programación ; en lenguajes de programación de tipado estático (o fuertemente tipado), el tipo de un puntero determina el tipo del dato al que apunta el puntero.
Los punteros son una abstracción muy delgada que se suma a las capacidades de direccionamiento que ofrecen la mayoría de las arquitecturas modernas . En el esquema más simple, se asigna una dirección o un índice numérico a cada unidad de memoria del sistema, donde la unidad suele ser un byte o una palabra (dependiendo de si la arquitectura es direccionable por bytes o por palabras ), lo que transforma efectivamente toda la memoria en una matriz muy grande . El sistema también proporcionaría una operación para recuperar el valor almacenado en la unidad de memoria en una dirección dada (normalmente utilizando los registros de propósito general de la máquina ).
En el caso habitual, un puntero es lo suficientemente grande como para albergar 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 , llamarse fallo de segmentación (segfault). El segundo caso es posible en la implementación actual de AMD64 , donde los punteros tienen una longitud de 64 bits y las direcciones solo se extienden a 48 bits. Los punteros deben cumplir ciertas reglas (direcciones canónicas), por lo que si se desreferencia un puntero no canónico, el procesador lanza un fallo de protección general .
Por otra parte, algunos sistemas tienen más unidades de memoria que direcciones. En este caso, se emplea un esquema más complejo, como la segmentación de memoria o la paginación , para utilizar diferentes partes de la memoria en diferentes momentos. Las últimas encarnaciones 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 . De este modo, solo se puede acceder a 1/16 de la memoria total posible 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 segmento y dirección de 16 bits hacía que el acceso a más de 64 KB en una estructura de datos fuera engorroso.
Para proporcionar una interfaz consistente, algunas arquitecturas proporcionan E/S mapeadas 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 archivos, índices de matrices y referencias a objetos remotos, que cumplen algunos de los mismos propósitos que las direcciones para otros tipos de objetos.
Los punteros se admiten directamente sin restricciones en lenguajes como PL/I , C , C++ , Pascal , FreeBASIC y de forma implícita en la mayoría de los lenguajes ensambladores . Se utilizan principalmente para construir referencias , que a su vez son fundamentales para construir casi todas las estructuras de datos , así como para pasar datos entre diferentes partes de un programa.
En los lenguajes de programación funcional que dependen en gran medida de listas, las referencias de datos se gestionan de forma abstracta mediante el uso de 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 "sabor" idiomático de la programación funcional. Al estructurar los datos en dichas listas cons , estos lenguajes facilitan los medios recursivos para construir y procesar datos, por ejemplo, accediendo de forma recursiva 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 alguna aproximación de una matriz de direcciones de memoria facilita el tratamiento de las variables como ranuras en las que se pueden asignar datos de forma imperativa .
Cuando se trabaja con matrices, la operación de búsqueda crítica generalmente implica una etapa llamada cálculo de dirección , que implica la construcción de un puntero al elemento de datos deseado en la matriz. 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 de una función a un parámetro sean visibles para el invocador de la función. Esto también resulta útil para devolver múltiples valores de una función.
Los punteros también se pueden utilizar para asignar y desasignar variables dinámicas y matrices en la memoria. Dado que una variable a menudo se vuelve redundante después de haber cumplido su propósito, es un desperdicio de memoria mantenerla y, por lo tanto, es una buena práctica desasignarla (utilizando la referencia del puntero original) cuando ya no se necesita. Si no se hace esto, se puede producir una pérdida de memoria (donde la memoria libre disponible disminuye gradualmente, o en casos graves rápidamente, debido a una acumulación de numerosos bloques de memoria redundantes).
La sintaxis básica para definir un puntero es: [4]
int * ptr ;
Esto declara ptr
como identificador de un objeto del siguiente tipo:
int
Generalmente esto se expresa de forma más sucinta como " ptr
es un puntero a int
".
Debido a que el lenguaje C no especifica una inicialización implícita para objetos de duración de almacenamiento automático, [5] a menudo se debe tener cuidado para asegurar que la dirección a la que ptr
apunta sea válida; es por esto que a veces se sugiere que un puntero se inicialice explícitamente al valor de puntero nulo , que tradicionalmente se especifica en C con la macro estandarizada NULL
: [6]
int * ptr = NULL ;
Desreferenciar un puntero nulo en C produce un comportamiento indefinido , [7] lo que podría ser catastrófico. Sin embargo, la mayoría de las implementaciones [ cita requerida ] simplemente detienen la ejecución del programa en cuestión, generalmente con un error de segmentación .
Sin embargo, inicializar punteros innecesariamente podría dificultar el análisis del programa, ocultando así errores.
En cualquier caso, una vez que se ha declarado un puntero, el siguiente paso lógico es que apunte a algo:
int a = 5 ; int * ptr = NULL ; ptr = &a a ;
Esto asigna el valor de la dirección de a
a ptr
. Por ejemplo, si a
se almacena en la ubicación de memoria 0x8130, entonces el valor de ptr
será 0x8130 después de la asignación. Para desreferenciar el puntero, se vuelve a utilizar un asterisco:
* ptr = 8 ;
Esto significa tomar el contenido de ptr
(que es 0x8130), "localizar" esa dirección en la memoria y establecer su valor en 8. Si a
luego se accede nuevamente, su nuevo valor será 8.
Este ejemplo puede resultar más claro si se examina directamente la memoria. Supongamos que a
se encuentra en la dirección 0x8130 en la memoria y ptr
en 0x8134; supongamos también que se trata de una máquina de 32 bits, de modo que un int tiene 32 bits de ancho. Esto es lo que habría en la memoria después de ejecutar el siguiente fragmento de código:
int a = 5 ; int * ptr = NULL ;
DIRECCIÓN | Contenido |
---|---|
0x8130 | 0x00000005 |
0x8134 | 0x00000000 |
(El puntero NULL que se muestra aquí es 0x00000000). Al asignar la dirección de a
a ptr
:
ptr = &a a ;
produce los siguientes valores de memoria:
DIRECCIÓN | Contenido |
---|---|
0x8130 | 0x00000005 |
0x8134 | 0x00008130 |
Luego, desreferenciando ptr
mediante codificación:
* ptr = 8 ;
La computadora tomará el contenido de ptr
(que es 0x8130), "localizará" esa dirección y asignará 8 a esa ubicación produciendo la siguiente memoria:
DIRECCIÓN | Contenido |
---|---|
0x8130 | 0x00000008 |
0x8134 | 0x00008130 |
Claramente, al acceder a
se obtendrá el valor 8 porque la instrucción anterior modificó el contenido de a
mediante el puntero ptr
.
Al configurar estructuras de datos como listas , colas y árboles, es necesario tener punteros para ayudar a administrar cómo se implementa y controla la estructura. 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 normalmente usa menos bits que una dirección completa, pero que generalmente requerirá una operación aritmética adicional para resolverse).
Las direcciones relativas son una forma de segmentación manual de la memoria y comparten muchas de sus ventajas y desventajas. Se puede utilizar un desplazamiento de dos bytes, que contiene un entero sin signo de 16 bits, para proporcionar direccionamiento relativo para hasta 64 KiB (2 16 bytes) de una estructura de datos. Esto se puede ampliar fácilmente a 128, 256 o 512 KiB si se fuerza a la dirección a la que se apunta a alinearse en un límite de media palabra, palabra o doble palabra (pero requiriendo una operación adicional de "desplazamiento a la izquierda" bit a bit —en 1, 2 o 3 bits— para ajustar el desplazamiento en un factor de 2, 4 u 8, antes de su adición a la dirección base). Sin embargo, en general, estos esquemas son muy problemáticos y, por conveniencia para el programador, se prefieren las direcciones absolutas (y, subyacente a esto, un espacio de direcciones plano ).
Un desplazamiento de un byte, como el valor ASCII hexadecimal de un carácter (por ejemplo, X'29') se puede utilizar para señalar un valor entero alternativo (o índice) en una matriz (por ejemplo, X'01'). De esta manera, los caracteres se pueden traducir de manera muy eficiente desde " datos sin procesar " a un índice secuencial utilizable y luego a una dirección absoluta sin una tabla de búsqueda .
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 array[i]
sea equivalente a *(array + i)
. [8] Por lo tanto, en C, las matrices pueden considerarse punteros a áreas consecutivas de memoria (sin espacios vacíos), [8] y la sintaxis para acceder a las matrices es idéntica a la que se puede utilizar para desreferenciar punteros. Por ejemplo, una matriz array
se puede declarar y utilizar de la siguiente manera:
int array [ 5 ]; /* Declara 5 enteros contiguos */ int * ptr = array ; /* Los arrays se pueden usar como punteros */ ptr [ 0 ] = 1 ; /* Los punteros se pueden indexar con sintaxis de array */ * ( array + 1 ) = 2 ; /* Los arrays se pueden desreferenciar con sintaxis de puntero */ * ( 1 + array ) = 2 ; /* La suma de punteros es conmutativa */ 2 [ array ] = 4 ; /* El operador de subíndice es conmutativo */
Esto asigna un bloque de cinco números enteros y nombra el bloque array
, que actúa como un puntero al bloque. Otro uso común de los punteros es señalar la memoria asignada dinámicamente desde malloc , que devuelve un bloque consecutivo de memoria de no menos del tamaño solicitado que se puede usar como una matriz.
Si bien la mayoría de los operadores en matrices y punteros son equivalentes, el resultado del sizeof
operador difiere. En este ejemplo, sizeof(array)
se evaluará como 5*sizeof(int)
(el tamaño de la matriz), mientras que sizeof(ptr)
se evaluará como sizeof(int*)
, el tamaño del puntero en sí.
Los valores predeterminados de una matriz se pueden declarar de la siguiente manera:
int matriz [ 5 ] = { 2 , 4 , 3 , 1 , 5 };
Si se encuentra en la memoria a partir de la dirección 0x1000 en una máquina little-endian dearray
32 bits , entonces la memoria contendrá lo siguiente (los valores están en hexadecimal , como las direcciones):
0 | 1 | 2 | 3 | |
---|---|---|---|---|
1000 | 2 | 0 | 0 | 0 |
1004 | 4 | 0 | 0 | 0 |
1008 | 3 | 0 | 0 | 0 |
100 °C | 1 | 0 | 0 | 0 |
1010 | 5 | 0 | 0 | 0 |
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 con el byte menos significativo almacenado primero (esta es una arquitectura de CPU little-endian ) y se almacenan consecutivamente comenzando en la dirección 0x1000.
La sintaxis para C con punteros es:
array
significa 0x1000;array + 1
significa 0x1004: el "+ 1" significa agregar el tamaño de 1 int
, que son 4 bytes;*array
significa desreferenciar el contenido de array
. Considerando el contenido como una dirección de memoria (0x1000), busque el valor en esa ubicación (0x0002);array[i]
significa elemento número i
, basado en 0, array
cuyo se traduce a *(array + i)
.El último ejemplo muestra cómo acceder al contenido de array
. Desglosándolo:
array + i
es la ubicación de memoria del (i) ésimo elemento de array
, comenzando en i=0;*(array + i)
toma esa dirección de memoria y la desreferencia para acceder al valor.A continuación se muestra un ejemplo de definición de una lista enlazada en C.
/* la lista enlazada vacía está representada por NULL * o algún otro valor centinela */ #define EMPTY_LIST NULLstruct link { void * data ; /* datos de este enlace */ struct link * next ; /* próximo enlace; EMPTY_LIST si no hay ninguno */ };
Esta definición de puntero recursivo es esencialmente la misma que la definición de referencia recursiva del lenguaje de programación Haskell :
Enlace de datos a = Nil | Cons a ( Enlace a )
Nil
es la lista vacía, y Cons a (Link a)
es una celda cons de tipo a
con otro enlace también de tipo a
.
Sin embargo, la definición con referencias se verifica por 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 contenedoras , cuya corrección se comprueba cuidadosamente.
Los punteros se pueden utilizar 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 int n dentro de la función sin afectar el código de llamada */ 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 principal ( vacío ) { int x = 3 ; /* pasa una copia del valor de x como argumento */ passByValue ( x ); // el valor se cambió dentro de la función, pero x sigue siendo 3 de aquí en adelante /* pasa la dirección de x como argumento */ passByAddress ( & x ); // x en realidad fue cambiado por la función y ahora es igual a 14 aquí devuelve 0 ; }
En algunos programas, la cantidad de memoria necesaria depende de lo que el usuario pueda introducir. En tales casos, el programador necesita asignar memoria dinámicamente. Esto se hace asignando memoria en el montón en lugar de en la pila , donde normalmente se almacenan las variables (aunque las variables también se pueden almacenar en los registros de la CPU). La asignación dinámica de memoria solo se puede realizar a través de punteros y no se pueden dar nombres, como ocurre con 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 almacén libre , desde la que se asignan objetos dinámicamente.
El código C de ejemplo que se muestra a continuación ilustra cómo se asignan y referencian dinámicamente los objetos de estructura. La biblioteca C estándar proporciona la función malloc()
para asignar bloques de memoria desde el montón. Toma el tamaño de un objeto para asignar como parámetro y devuelve un puntero a un bloque de memoria recién asignado adecuado para almacenar el objeto, o devuelve un puntero nulo si la asignación falló.
/* Artículo del inventario de piezas */ struct Item { int id ; /* Número de pieza */ char * name ; /* Nombre de la pieza */ float cost ; /* Costo */ }; /* Asignar e inicializar un nuevo objeto Item */ struct Item * make_item ( const char * name ) { struct Item * item ; /* Asignar un bloque de memoria para un nuevo objeto Item */ item = malloc ( sizeof ( struct Item )); if ( item == NULL ) return NULL ; /* Inicializar los miembros del nuevo elemento */ memset ( item , 0 , sizeof ( struct Item )); item -> id = -1 ; item -> name = NULL ; item -> cost = 0.0 ; /* Guardar una copia del nombre en el nuevo elemento */ elemento -> nombre = malloc ( strlen ( nombre ) + 1 ); if ( elemento -> nombre == NULL ) { free ( elemento ); return NULL ; } strcpy ( elemento -> nombre , nombre ); /* Devuelve el objeto Item recién creado */ return item ; }
El código siguiente ilustra cómo se desasignan dinámicamente los objetos de memoria, es decir, se devuelven al montón o al almacén libre. La biblioteca estándar de C proporciona la función free()
para desasignar un bloque de memoria previamente asignado y devolverlo al montón.
/* Desasignar un objeto Item */ void destroy_item ( struct Item * item ) { /* Verificar si hay un puntero a objeto nulo */ if ( item == NULL ) return ; /* Desasignar la cadena de nombre guardada dentro del elemento */ if ( item -> name != NULL ) { free ( item -> name ); item -> name = NULL ; } /* Desasignar el objeto Item en sí */ free ( item ); }
En algunas arquitecturas informáticas, se pueden utilizar punteros para manipular directamente la memoria o los dispositivos mapeados en la 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 los años 80, el uso del BIOS para acceder a las capacidades de vídeo de las PC era lento. Las aplicaciones que hací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 de 16 bits sin signo. Cada valor consistía en un código ASCII en el byte inferior y un color en el byte superior. Por lo tanto, para poner 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' ; }
Las tablas de control que se utilizan para controlar el flujo de un programa suelen hacer un uso extensivo de punteros. Los punteros, normalmente integrados en una entrada de la tabla, pueden utilizarse, por ejemplo, para contener los puntos de entrada a las subrutinas que se van a ejecutar, en función de ciertas condiciones definidas en la misma entrada de la tabla. Sin embargo, los punteros pueden ser simplemente índices de otras tablas separadas, pero asociadas, que comprenden una matriz de las direcciones reales o las direcciones mismas (según las construcciones del lenguaje de programación disponibles). También pueden utilizarse para señalar entradas anteriores de la tabla (como en el procesamiento de bucles) o para saltarse algunas entradas de la tabla (como en un cambio o una salida "anticipada" de un bucle). Para este último propósito, el "puntero" puede ser simplemente el número de entrada de la tabla en sí y puede transformarse en una dirección real mediante una simple operación aritmética.
En muchos lenguajes, los punteros tienen la restricción adicional de que el objeto al que apuntan tiene un tipo específico . Por ejemplo, un puntero puede declararse para que apunte a un entero ; el lenguaje intentará entonces evitar que el programador lo apunte a objetos que no sean enteros, como números de punto flotante , eliminando así algunos errores.
Por ejemplo, en C
int * dinero ; char * bolsas ;
money
sería un puntero entero y bags
sería un puntero de carácter. Lo siguiente generaría una advertencia del compilador de "asignación de un tipo de puntero incompatible" en GCC
bolsas = dinero ;
Porque money
y bags
se declararon con tipos diferentes. Para suprimir la advertencia del compilador, debe quedar explícito que realmente desea realizar la asignación mediante la conversión de tipos .
bolsas = ( char * ) dinero ;
que dice convertir el puntero entero de money
a un puntero char 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 uno de otro tipo debe mantener la corrección de alineación para ambos tipos (6.3.2.3 Punteros, párrafo 7): [9]
char * búfer_externo = "abcdef" ; int * datos_internos ; internal_data = ( int * ) external_buffer ; // COMPORTAMIENTO INDEFINIDO si "el puntero resultante // no está alineado correctamente"
En los lenguajes que permiten la aritmética de punteros, la aritmética de 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 una matriz de un tipo determinado, como se mostró en el ejemplo de matrices de C anterior. Cuando un puntero de un tipo se convierte en otro tipo de un tamaño diferente, el programador debe esperar que la aritmética de punteros se calcule de forma diferente. En C, por ejemplo, si la money
matriz comienza en 0x2000 y sizeof(int)
tiene 4 bytes mientras que sizeof(char)
es de 1 byte, entonces money + 1
apuntará a 0x2004, pero bags + 1
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, bags[0] = 65537;
), resultados inesperados cuando se desplazan valores en bits y problemas de comparación, especialmente con valores con signo y sin signo.
Aunque en general es imposible determinar en tiempo de compilación qué conversiones son seguras, algunos lenguajes almacenan información de tipo en tiempo de ejecución que se puede utilizar para confirmar que estas conversiones peligrosas son válidas en tiempo de ejecución. Otros lenguajes simplemente aceptan una aproximación conservadora de conversiones seguras, o ninguna.
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 "solo porque dos punteros apuntan 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 conoce como su procedencia . [10] La conversión a un tipo entero como uintptr_t
está definido por la implementación y la comparación que proporciona no proporciona más información sobre si los dos punteros son intercambiables. Además, una mayor conversión 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. [11]
Como 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 resultar 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 algunas de sus desventajas , también conocidas a veces como peligros de puntero . En este contexto, los punteros que se dirigen directamente a la memoria (como se usa en este artículo) se conocen comopunteros sin formato , en contraste conlos punteros inteligentesu otras variantes.
Un problema importante con los punteros es que, siempre que se puedan manipular directamente como un número, se puede hacer que apunten 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 imperativos recientes como Java , reemplazan los punteros con un tipo de referencia más opaco, normalmente denominado simplemente referencia , que solo se puede utilizar para hacer referencia a objetos y no se puede manipular como números, lo que evita este tipo de error. La indexación de matrices se maneja como un caso especial.
Un puntero que no tiene ninguna dirección asignada se denomina puntero salvaje . Cualquier intento de utilizar dichos 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 error de segmentación , una violación de almacenamiento o una bifurcación salvaje (si se utiliza como puntero de función o dirección de bifurcación).
En sistemas con asignación explícita de memoria, es posible crear un puntero colgante desasignando la región de memoria a la que apunta. Este tipo de puntero es peligroso y sutil porque una región de memoria desasignada puede contener los mismos datos que tenía antes de que se desasignara, pero puede ser reasignada y sobrescrita por código no relacionado, desconocido para el código anterior. Los lenguajes con recolección de basura evitan este tipo de error porque la desasignación se realiza automáticamente cuando no hay más referencias en el ámbito.
Algunos lenguajes, como C++ , admiten punteros inteligentes , que utilizan una forma sencilla de conteo de referencias para ayudar a rastrear la asignación de memoria dinámica además de actuar como referencia. En ausencia de ciclos de referencia, donde un objeto se refiere 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 .
Un puntero nulo tiene un valor reservado para indicar que el puntero no hace referencia a un objeto válido. Los punteros nulos se utilizan habitualmente para representar condiciones como el final de una lista de longitud desconocida o la imposibilidad de realizar alguna acción; este uso de punteros nulos se puede comparar con los tipos que aceptan valores nulos y con el valor Nothing en un tipo de opción .
Un puntero colgante es un puntero que no apunta a un objeto válido y, en consecuencia, puede hacer que un programa se bloquee o se comporte de manera extraña. En los lenguajes de programación Pascal o C , los punteros que no están inicializados específicamente pueden apuntar a direcciones impredecibles en la memoria.
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, asumiendo que malloc() no ha devuelto NULL. */ * p2 = 'b' ; /* Esto invoca un comportamiento indefinido */ }
Aquí, p2
puede apuntar a cualquier lugar de la memoria, por lo que realizar la asignación *p2 = 'b';
puede dañar un área desconocida de la memoria o provocar una falla de segmentación .
Cuando se utiliza un puntero como dirección del punto de entrada a un programa o inicio de una función que no devuelve nada y que además no está inicializada o está dañada, si se realiza una llamada o un salto a esta dirección, se dice que se ha producido una " rama salvaje ". En otras palabras, una rama salvaje es un puntero de función que es salvaje (que está colgando).
Las consecuencias suelen ser impredecibles y el error puede presentarse de varias formas diferentes dependiendo de si el puntero es una dirección "válida" o no y de si hay o no (casualmente) una instrucción válida (código de operación) en esa dirección. La detección de una bifurcación salvaje puede presentar uno de los ejercicios de depuración más difíciles y frustrantes, ya que gran parte de la evidencia puede haber sido destruida de antemano 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 normalmente no solo puede detectar una bifurcación salvaje antes de que surta efecto, sino que también puede proporcionar un rastro completo o parcial de su historial.
Un puntero autorrelativo es un puntero cuyo valor se interpreta como un desplazamiento desde la dirección del puntero mismo; por lo tanto, si una estructura de datos tiene un miembro puntero autorrelativo que apunta a alguna porción de la estructura de datos misma, entonces la estructura de datos puede reubicarse en la memoria sin tener que actualizar el valor del puntero autorrelativo. [12]
La patente citada también utiliza el término puntero autorrelativo para significar lo mismo. Sin embargo, el significado de ese término se ha utilizado de otras maneras:
Un puntero base es un puntero cuyo valor es un desplazamiento del valor de otro puntero. Puede utilizarse para almacenar y cargar bloques de datos, asignando la dirección del comienzo del bloque al puntero base. [14]
En algunos lenguajes, un puntero puede hacer referencia a otro puntero, lo que requiere múltiples operaciones de desreferenciación para llegar al valor original. Si bien cada nivel de indirección puede agregar un costo de rendimiento, a veces es necesario para proporcionar un comportamiento correcto para estructuras de datos complejas . Por ejemplo, en C es típico definir una lista enlazada en términos de un elemento que contiene un puntero al siguiente elemento de la lista:
elemento de estructura { elemento de estructura * siguiente ; int valor ; }; elemento de estructura * cabeza = NULL ;
Esta implementación utiliza un puntero al primer elemento de la lista como sustituto de toda la lista. Si se agrega un nuevo valor al principio de la lista, head
se debe cambiar para que apunte al nuevo elemento. Dado que los argumentos de C siempre se pasan por valor, el uso de la indirección doble permite que la inserción se implemente correctamente y tiene el efecto secundario deseable de eliminar el código de caso especial para tratar las inserciones al principio de la lista:
// Dada una lista ordenada en *head, inserte el elemento item en la primera // ubicación donde todos los elementos anteriores tienen un valor menor o igual. void insert ( struct element ** head , struct element * item ) { struct element ** p ; // p apunta a un puntero a un elemento for ( p = head ; * p != NULL ; p = & ( * p ) -> next ) { if ( item -> value <= ( * p ) -> value ) break ; } item -> next = * p ; * p = item ; } // El llamador hace esto: insert ( & head , item );
En este caso, si el valor de item
es menor que el de head
, el de la persona que llama head
se actualiza correctamente a la dirección del nuevo elemento.
Un ejemplo básico está en el argumento argv de la función principal en C (y C++) , que se da en el prototipo como char **argv
—esto se debe a que la variable argv
en sí es un puntero a una matriz de cadenas (una matriz de matrices), por lo que *argv
es un puntero a la cadena 0 (por convención, el nombre del programa), y **argv
es el carácter 0 de la cadena 0.
En algunos lenguajes, un puntero puede hacer referencia a código ejecutable, es decir, puede apuntar a una función, método o procedimiento. Un puntero de función almacenará la dirección de una función que se invocará. Si bien esta función se puede utilizar para llamar a funciones de forma dinámica, suele ser una técnica favorita de los creadores de virus y otros programas maliciosos.
int suma ( int n1 , int n2 ) { // Función con dos parámetros enteros que devuelven un valor entero return n1 + n2 ; } int main ( void ) { int a , b , x , y ; int ( * fp )( int , int ); // Puntero de función que puede apuntar a una función como suma fp = & sum ; // fp ahora apunta a la función suma x = ( * fp )( a , b ); // Llama a la función suma con argumentos a y b y = sum ( a , b ); // Llama a la función suma con argumentos a y b }
En las listas doblemente enlazadas o las estructuras de árbol , un puntero hacia atrás colocado sobre un elemento "apunta hacia atrás" al elemento que hace referencia al elemento actual. Son útiles para la navegación y la manipulación, a costa de un mayor uso de memoria.
Es posible simular el comportamiento de un puntero utilizando un índice de una matriz (normalmente unidimensional).
Principalmente para lenguajes que no soportan punteros explícitamente pero sí soportan arreglos, el arreglo puede ser pensado y procesado como si fuera el rango completo de memoria (dentro del alcance del arreglo particular) y cualquier índice al mismo puede ser pensado como equivalente a un registro de propósito general en lenguaje ensamblador (que apunta a los bytes individuales pero cuyo valor real es relativo al inicio del arreglo, no a su dirección absoluta en memoria). Suponiendo que el arreglo es, digamos, una estructura de datos de caracteres contiguos de 16 megabytes , los bytes individuales (o una cadena de bytes contiguos dentro del arreglo) pueden ser direccionados y manipulados directamente usando el nombre del arreglo con un entero sin signo de 31 bits como el puntero simulado (esto es bastante similar al ejemplo de arreglos de C mostrado arriba). La aritmética de punteros puede ser simulada sumando o restando del índice, con una sobrecarga adicional mínima comparada con la aritmética de punteros genuina.
Incluso es teóricamente posible, utilizando la técnica anterior, junto con un simulador de conjunto de instrucciones adecuado , simular cualquier código de máquina o el intermedio ( código de bytes ) de cualquier procesador/lenguaje en otro lenguaje que no admita punteros en absoluto (por ejemplo, Java / JavaScript ). Para lograr esto, el código binario se puede cargar inicialmente en bytes contiguos de la matriz para que el simulador lo "lea", interprete y actúe completamente dentro de la memoria contenida en la misma matriz. Si es necesario, para evitar por completo los problemas de desbordamiento de búfer , la comprobación de límites generalmente se puede ejecutar para el compilador (o si no, se puede codificar manualmente en el simulador).
Ada es un lenguaje fuertemente tipado en el que todos los punteros están tipados y solo se permiten conversiones de tipos seguras. Todos los punteros se inicializan de forma predeterminada en null
, y cualquier intento de acceder a los datos a través de un null
puntero provoca que se genere una excepción . Los punteros en Ada se denominan tipos de acceso . Ada 83 no permitía la aritmética en los tipos de acceso (aunque muchos proveedores de compiladores la proporcionaban como una característica no estándar), pero Ada 95 admite la aritmética "segura" en los tipos de acceso a través del paquete System.Storage_Elements
.
Varias versiones antiguas de BASIC para la plataforma Windows admitían STRPTR() para devolver la dirección de una cadena y VARPTR() para devolver la dirección de una variable. Visual Basic 5 también admitía OBJPTR() para devolver la dirección de una interfaz de objeto y un operador ADDRESSOF para devolver la dirección de una función. Los tipos de todos estos son números enteros, pero sus valores son equivalentes a los de los tipos de puntero.
Sin embargo, los dialectos más nuevos de BASIC , como FreeBASIC o BlitzMax , tienen implementaciones exhaustivas de punteros. En FreeBASIC, la aritmética sobre ANY
punteros (equivalente a la de C void*
) se trata como si el ANY
puntero tuviera un ancho de byte. ANY
Los punteros no se pueden desreferenciar, como en C. Además, la conversión entre ANY
y punteros de cualquier otro tipo no generará ninguna advertencia.
dim como entero f = 257 dim como cualquier ptr g = @ f dim como entero ptr i = g assert ( * i = 257 ) assert ( ( g + 4 ) = ( @ f + 1 ) )
En C y C++, los punteros son variables que almacenan direcciones y pueden ser null . Cada puntero tiene un tipo al que apunta, pero se puede realizar una conversión libremente entre tipos de puntero (pero no entre un puntero de función y un puntero de objeto). Un tipo de puntero especial llamado "puntero void" permite apuntar a cualquier objeto (no de función), pero está limitado por el hecho de que no se puede desreferenciar directamente (se debe realizar una conversión). La dirección en sí misma a menudo se puede manipular directamente mediante la conversión de un puntero hacia y desde un tipo integral de tamaño suficiente, aunque los resultados están definidos por la implementación y pueden causar un comportamiento indefinido; mientras que los estándares de C anteriores no tenían un tipo integral que se garantizara que fuera lo suficientemente grande, C99 especifica el nombre uintptr_t
de typedef definido en <stdint.h>
, pero una implementación no necesita proporcionarlo.
C++ es totalmente compatible con los punteros de C y la conversión de tipos de C. También es compatible con un nuevo grupo de operadores de conversión de tipos para ayudar a detectar algunas conversiones peligrosas no deseadas en tiempo de compilación. Desde C++11 , la biblioteca estándar de C++ también proporciona punteros inteligentes ( unique_ptr
, shared_ptr
y weak_ptr
) que se pueden utilizar en algunas situaciones como una alternativa más segura a los punteros primitivos de C. C++ también es compatible con otra forma de referencia, bastante diferente de un puntero, llamada simplemente referencia o tipo de referencia .
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 único 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 por un múltiplo del tamaño de su tipo de datos . Por ejemplo, sumar 1 a un puntero a valores enteros de 4 bytes incrementará la dirección de byte apuntada del puntero en 4. Esto tiene el efecto de incrementar el puntero para apuntar al siguiente elemento en una matriz contigua de enteros, que a menudo es el resultado deseado. La aritmética de punteros no se puede realizar en void
punteros porque el tipo void no tiene tamaño y, por lo tanto, no se puede agregar a la dirección apuntada, aunque gcc y otros compiladores realizarán la 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 tratar con diferentes tipos: sumando y restando el número de elementos requeridos en lugar del desplazamiento real en bytes. (La aritmética de punteros con char *
punteros utiliza desplazamientos de bytes, porque sizeof(char)
es 1 por definición). En particular, la definición de C declara explícitamente que la sintaxis a[n]
, que es el n
-ésimo elemento de la matriz a
, es equivalente a *(a + n)
, que es el contenido del elemento al que apunta a + n
. Esto implica que n[a]
es equivalente a a[n]
, y se puede escribir, por ejemplo, a[3]
o 3[a]
igualmente bien para acceder al cuarto elemento de una matriz a
.
Aunque es potente, la aritmética de punteros puede ser una fuente de errores informáticos . Tiende a confundir a los programadores novatos , obligándolos a utilizar contextos diferentes: una expresión puede ser una aritmética ordinaria o una aritmética de punteros, y a veces es fácil confundir una con la otra. En respuesta a esto, muchos lenguajes informáticos modernos de alto nivel (por ejemplo, Java ) no permiten el acceso directo a la memoria mediante direcciones. Además, el dialecto seguro de C Cyclone soluciona muchos de los problemas con los punteros. Consulte el lenguaje de programación C para obtener más información.
El void
puntero , o void*
, se admite en ANSI C y C++ como un tipo de puntero genérico. Un puntero a void
puede almacenar la dirección de cualquier objeto (no función), [a] y, en C, se convierte implícitamente a cualquier otro tipo de puntero de objeto en la asignación, pero se debe convertir explícitamente si se desreferencia. K&R C utilizado char*
para el propósito de "puntero independiente del tipo" (antes de ANSI C).
int x = 4 ; void * p1 = & x ; int * p2 = p1 ; // void* implícitamente convertido a int*: C válido, pero no C++ int a = * p2 ; int b = * ( int * ) p1 ; // al desreferenciar en línea, no hay conversión implícita
C++ no permite la conversión implícita de void*
a otros tipos de punteros, ni siquiera en asignaciones. Esta fue una decisión de diseño para evitar conversiones descuidadas e incluso no intencionadas, aunque la mayoría de los compiladores solo muestran advertencias, no errores, cuando encuentran otras conversiones.
int x = 4 ; void * p1 = & x ; int * p2 = p1 ; // esto falla en C++: no hay conversión implícita de void* int * p3 = ( int * ) p1 ; // Conversión al estilo C int * p4 = reinterpret_cast < int *> ( p1 ); // Conversión 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
.
En C++ se pueden definir punteros a miembros no estáticos de una clase. Si una clase C
tiene un miembro T a
, entonces &C::a
es un puntero al miembro a
de tipo T C::*
. Este miembro puede ser un objeto o una función . [16] Se pueden utilizar en el lado derecho de los operadores .*
y ->*
para acceder al miembro correspondiente.
estructura S { int a ; int f () const { devolver a ;} }; S s1 {}; S * ptrS = & s1 ; int S ::* ptr = & S :: a ; // puntero a S::a int ( S ::* fp )() const = & S :: f ; // puntero a S::f s1 . * ptr = 1 ; std :: cout << ( s1 . * fp )() << " \n " ; // imprime 1 ptrS ->* ptr = 2 ; std :: cout << ( ptrS ->* fp )() << " \n " ; // imprime 2
Estas declaraciones de punteros cubren la mayoría de las variantes de declaraciones de punteros. Por supuesto, es posible tener punteros triples, pero los principios fundamentales detrás de un puntero triple ya existen en un puntero doble. La denominación utilizada aquí es lo que typeid(type).name()
equivale la expresión para cada uno de estos tipos cuando se utiliza g++ o clang . [17] [18]
char A5_A5_c [ 5 ][ 5 ]; /* matriz de matrices de caracteres */ char * A5_Pc [ 5 ]; /* matriz de punteros a caracteres */ char ** PPc ; /* puntero a puntero a carácter ("puntero doble") */ char ( * PA5_c ) [ 5 ]; /* puntero a matriz(s) de caracteres */ char * FPcvE (); /* función que devuelve un puntero a carácter(es) */ char ( * PFcvE )(); /* puntero a una función que devuelve un carácter */ char ( * FPA5_cvE ())[ 5 ]; /* función que devuelve un puntero a una matriz de caracteres */ char ( * A5_PFcvE [ 5 ])(); /* una matriz de punteros a funciones que devuelven un carácter */
Las siguientes declaraciones que involucran punteros a miembros solo son válidas en C++:
clase C ; clase D ; char C ::* M1Cc ; /* puntero a miembro a char */ char C ::* A5_M1Cc [ 5 ]; /* matriz de punteros a miembro a char */ char * C ::* M1CPc ; /* puntero a miembro a puntero a char(s) */ char C ::** PM1Cc ; /* puntero a puntero a miembro a char */ char ( * M1CA5_c ) [ 5 ]; /* puntero a miembro a matriz(s) de caracteres */ char C ::* FM1CcvE (); /* función que devuelve un puntero a miembro a char */ char D ::* C ::* M1CM1Dc ; /* puntero a miembro a puntero a miembro a puntero a char(s) */ char C ::* C ::* M1CMS_c ; /* puntero a miembro a puntero a miembro a puntero a char(s) */ char ( C ::* FM1CA5_cvE ())[ 5 ]; /* función que devuelve puntero a miembro a una matriz de caracteres */ char ( C ::* M1CFcvE )() /* función puntero a miembro que devuelve un carácter */ char ( C ::* A5_M1CFcvE [ 5 ])(); /* una matriz de punteros a funciones miembro que devuelven un carácter */
Los ()
y []
tienen una prioridad mayor que *
. [19]
En el lenguaje de programación C# , los punteros se admiten marcando bloques de código que incluyen punteros con la unsafe
palabra clave o mediante using
las System.Runtime.CompilerServices
disposiciones del ensamblado para el acceso a punteros. La sintaxis es esencialmente la misma que en C++ y la dirección a la que se apunta puede ser memoria administrada o no administrada . Sin embargo, los punteros a memoria administrada (cualquier puntero a un objeto administrado) deben declararse utilizando la fixed
palabra clave, lo que evita que el recolector de elementos no utilizados mueva el objeto apuntado como parte de la administración de memoria mientras el puntero está dentro del ámbito, manteniendo así válida la dirección del puntero.
Sin embargo, una excepción a esto es el uso de la IntPtr
estructura, que es un equivalente administrado por memoria a int*
, y no requiere la unsafe
palabra clave ni el CompilerServices
ensamblaje. Este tipo se devuelve a menudo cuando se utilizan métodos de System.Runtime.InteropServices
, por ejemplo:
// Obtener 16 bytes de memoria de la memoria no administrada del proceso IntPtr pointer = System . Runtime . InteropServices . Marshal . AllocHGlobal ( 16 ); // Hacer algo con la memoria asignada// Libera la memoria asignada System . Runtime . InteropServices . Marshal . FreeHGlobal ( pointer );
El marco .NET incluye muchas clases y métodos en los espacios System
de nombres y System.Runtime.InteropServices
(como la Marshal
clase ) 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 lugares arbitrarios en la memoria.
El lenguaje de programación COBOL admite punteros a variables. Los objetos de datos primitivos o de grupo (registro) declarados dentro de LINKAGE SECTION
un programa están basados 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-STORAGE
variable, pero se accede a su contenido de forma implícita e indirecta a través de sus LINKAGE
punteros.
El espacio de memoria para cada objeto de datos señalado generalmente se asigna de forma dinámica mediante instrucciones externas CALL
o a través de construcciones de lenguaje extendidas integradas, como instrucciones EXEC CICS
o EXEC SQL
.
Las versiones extendidas de COBOL también proporcionan variables de puntero declaradas con USAGE
IS
POINTER
cláusulas. Los valores de dichas variables de puntero se establecen y modifican mediante instrucciones SET
y SET
ADDRESS
.
Algunas versiones extendidas de COBOL también proporcionan PROCEDURE-POINTER
variables, que son capaces de almacenar las direcciones del código ejecutable .
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 amplias funciones integradas . PL/I fue un gran avance en comparación con los lenguajes de programación de su tiempo. [ cita requerida ] Los punteros PL/I no tienen tipo y, por lo tanto, no se requiere conversión para la desreferenciación o asignación de punteros. La sintaxis de declaración para un puntero es DECLARE xxx POINTER;
, que declara un puntero llamado "xxx". Los punteros se utilizan con BASED
variables. Una variable basada se puede declarar con un localizador predeterminado ( DECLARE xxx BASED(ppp);
o sin ( DECLARE xxx BASED;
), donde xxx es una variable basada, 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 ( xxx=1;
, o se puede direccionar con una referencia explícita al localizador predeterminado (ppp), o a cualquier otro puntero ( qqq->xxx=1;
).
La aritmética de punteros no forma parte del estándar PL/I, pero muchos compiladores permiten expresiones de la forma ptr = ptr±expression
. IBM PL/I también tiene la función incorporada PTRADD
para realizar la aritmética. La aritmética de punteros siempre se realiza en bytes.
Los compiladores IBM Enterprise PL/I tienen una nueva forma de puntero tipado llamado HANDLE
.
El lenguaje de programación D es un derivado de C y C++ que admite plenamente los punteros de C y la conversión de tipos de C.
El lenguaje orientado a objetos Eiffel emplea semántica de valores y referencias sin aritmética de punteros. No obstante, se proporcionan clases de punteros. Ofrecen aritmética de punteros, conversión de tipos, gestión explícita de memoria, interconexión con software que no sea de Eiffel y otras funciones.
Fortran-90 introdujo una capacidad de puntero fuertemente tipado. Los punteros Fortran contienen más que una simple dirección de memoria. También encapsulan los límites superior e inferior de las dimensiones de la matriz, pasos (por ejemplo, para admitir secciones de matriz arbitrarias) y otros metadatos. Un operador de asociación , =>
se utiliza para asociar a POINTER
a una variable que tiene un TARGET
atributo. La declaración Fortran-90 ALLOCATE
también se puede utilizar para asociar un puntero a un bloque de memoria. Por ejemplo, el siguiente código se puede utilizar para definir y crear una estructura de lista enlazada:
tipo lista_real_t real :: sample_data ( 100 ) tipo ( lista_real_t ), puntero :: siguiente => null () fin tipo tipo ( lista_real_t ), objetivo :: mi_lista_real tipo ( lista_real_t ), puntero :: lista_real_temp real_list_temp => mi_lista_real hacer leer ( 1 , iostat = ioerr ) real_list_temp % datos_de_muestra si ( ioerr /= 0 ) salir asignar ( real_list_temp % siguiente ) real_list_temp => real_list_temp % siguiente fin hacer
Fortran-2003 agrega compatibilidad con punteros de procedimiento. Además, como parte de la característica de interoperabilidad de C , Fortran-2003 admite funciones intrínsecas para convertir punteros de estilo C en punteros Fortran y viceversa.
Go tiene punteros. Su sintaxis de declaración es equivalente a la de C, pero escrita al revés, terminando con el tipo. A diferencia de C, Go tiene recolección de basura y no permite la aritmética de punteros. Los tipos de referencia, como en C++, no existen. Algunos tipos integrados, como los mapas y los canales, están encajonados (es decir, internamente son punteros a estructuras mutables) y se inicializan utilizando la make
función. En un enfoque hacia la sintaxis unificada entre punteros y no punteros, ->
se ha eliminado el operador de flecha ( ): el operador de punto en un puntero se refiere al campo o método del objeto desreferenciado. Sin embargo, esto solo funciona con 1 nivel de indirección.
En Java no existe una representación explícita de punteros . En su lugar, se implementan estructuras de datos más complejas, como objetos y matrices , mediante referencias . El lenguaje no proporciona ningún operador explícito de manipulación de punteros. Sin embargo, aún es posible que el código intente desreferenciar una referencia nula (puntero nulo), lo que da como resultado que se lance una excepción en tiempo de ejecución . El espacio ocupado por objetos de memoria sin referencia se recupera automáticamente mediante la recolección de basura en tiempo de ejecución. [20]
Los punteros se implementan de forma muy similar a Pascal, al igual que VAR
los parámetros en las llamadas a procedimientos. Modula-2 está incluso más fuertemente tipado que Pascal, con menos formas de escapar del sistema de tipos. Algunas de las variantes de Modula-2 (como Modula-3 ) incluyen recolección de basura.
Al igual que con Modula-2, los punteros están disponibles. Aún hay menos formas de evadir el sistema de tipos y, por lo tanto, Oberon y sus variantes siguen siendo más seguros con respecto a los punteros que Modula-2 o sus variantes. Al igual que con Modula-3 , la recolección de basura es parte de la especificación del lenguaje.
A diferencia de muchos lenguajes que cuentan con punteros, el estándar ISO Pascal solo permite que los punteros hagan referencia a variables creadas dinámicamente que son anónimas y no les permite hacer referencia a variables estáticas o locales estándar. [21] 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 entero). Esto ayuda a eliminar los problemas de seguridad de tipos inherentes a otras implementaciones de punteros, particularmente las utilizadas para PL/I o C. También elimina algunos riesgos causados por punteros colgantes , pero la capacidad de dejar ir dinámicamente el espacio referenciado mediante el dispose
procedimiento estándar (que tiene el mismo efecto que la free
función de biblioteca que se encuentra en C ) significa que el riesgo de punteros colgantes no se ha eliminado por completo. [22]
Sin embargo, en algunas implementaciones de compiladores Pascal (o derivados) comerciales y de código abierto —como Free Pascal , [23] 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 de un puntero lo mueve esa cantidad de bytes en cualquier dirección, pero usar los procedimientos estándar Inc
o Dec
con él mueve el puntero por 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.
El lenguaje de programación Perl admite punteros, aunque rara vez se utilizan, en forma de funciones de empaquetado y desempaquetado. Estas funciones están pensadas únicamente para interacciones sencillas con bibliotecas compiladas del sistema operativo. En todos los demás casos, Perl utiliza referencias , que están tipificadas y no permiten ninguna forma de aritmética de punteros. Se utilizan para construir estructuras de datos complejas. [24]
void*
como una característica admitida condicionalmente y el estándar C dice que dichas conversiones son "extensiones comunes". Esto es requerido por la función POSIXdlsym
. [15]Puntero de Harold Lawson.
no contiene agujeros adicionales porque todos los demás tipos se compactan cuando se componen en matrices [en la página 51]
{{cite book}}
: |work=
ignorado ( ayuda ) .Mantenimiento CS1: nombres múltiples: lista de autores ( enlace ) Mantenimiento CS1: nombres numéricos: lista de autores ( enlace )