Puntero (informática)

De Wikipedia, la enciclopedia libre
Saltar a: navegación, búsqueda

Un puntero o apuntador es una variable que da referencia a una región de memoria; en otras palabras es una variable cuyo valor es una dirección de memoria. Si se tiene una variable ' p ' de tipo puntero que contiene una dirección de memoria en la que se encuentra almacenado un valor ' v ' se dice que ' p ' apunta a ' v '. El programador utilizará punteros para guardar datos en memoria en muchas ocasiones, de la forma que se describe a continuación.

           [Memoria]
          |    .    |
          |    .    |
          |    .    |
-----     |---------|
| p |---->|    v    |
-----     |---------|
          |    .    |
          |    .    |
          |    .    |

Trabajar con punteros no implica la manipulación de los datos en sí, sino manejar las direcciones de memoria en la cuales estos residen.

Introducción[editar]

Los punteros son de amplia utilización en programación y muchos lenguajes permiten la manipulación directa o indirecta de los mismos. La principal razón de ser de los punteros es la de manejar datos alojados en la zona de memoria dinámica o heap (aunque también se pueden manipular objetos en la zona estática), bien sean datos elementales, estructuras (struct en C) u objetos pertenecientes a una clase (en lenguajes Orientados a Objetos). Gracias a esta propiedad, los punteros permiten modelar un grafo, en donde los elementos de éste son los datos residentes en memoria y las relaciones entre los elementos son los propios apuntadores.

En nuevos lenguajes de alto nivel, los punteros se han tratado de abstraer. De tal forma que en el lenguaje C# sólo pueden ser usados en zonas de código delimitadas como "inseguras", o llegando a su total desaparición del código en lenguajes como Java o Eiffel.

Que no estén en el código no implica que no existan: internamente, la máquina virtual Java trata todas las variables que referencian objetos como punteros a zonas de memoria que realmente contienen los objetos. Esto puede causar ciertos efectos laterales si no se tiene en cuenta. De hecho, no es descabellado pensar que Java está utilizando punteros si cuando uno accede a una propiedad de un objeto no inicializado es lanzada la excepción NullPointerException.

Punteros de C[editar]

La sintaxis básica para definir un puntero es:[1]

int * ptr;

Esto declara ptr como el identificador de un objeto de la siguiente tipo:

  • puntero que apunta a un objeto de tipo int

Esto usualmente se manifiesta 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 los objetos de duración automática de almacenamiento,[2] frecuentemente se debe prestar atención para asegurarse de que la dirección a la que ptr puntea es válida; por eso a veces se sugiere que un puntero pueda ser explícitamente inicializado al valor de puntero nulo, que es tradicionalmente especificado en C con la macro estandarizado NULL:[3]

int *ptr = NULL;

Desreferenciar un puntero nulo en C produce un comportamiento indefinido,[4] 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, usualmente con un fallo de segmentación.

Sin embargo, punteros inicializados podría obstaculizar innecesariamente el análisis del programa, ocultando de ese modo los bugs.

En cualquier caso, una vez que un puntero ha sido declarado, el siguiente paso lógico es que se apunte a algo:

int a = 5;
int *ptr = NULL;
 
ptr = &a;

Esto asigna el valor de la dirección de a a ptr. Por ejemplo, si a está almacenado en la ubicación de memoria de 0x8130 entonces el valor de ptr será 0x8130 después de la asignación. Para eliminar la referencia al puntero, se utiliza de nuevo el asterisco:

*ptr = 8;

Esto significa tomar el contenido de ptr (que es 0x8130), "localizar" la dirección en memoria y establecer su valor en 8. Si, más tarde, se accede de nuevo, su nuevo valor será 8.

Este ejemplo puede ser más claro si la memoria no es directamente examinada. Supongamos que a se localiza en la dirección 0x8130 en memoria y ptr en 0x8134; también asume se trata de un equipo de 32 bits de tal manera que un int tiene un ancho de 32 bits. Lo que sigue es lo que estaría en la memoria después de que se ejecuta 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.) Mediante la asignación la direcciona de a a ptr:

 ptr = &a;

produce los siguientes valores de memoria:

Dirección Contenido
0x8130 0x00000005
0x8134 0x00008130

Entonces desreferenciando ptr mediante codificación:

 *ptr = 8;

la computadora tendrá el contenido de ptr (que es 0x8130), 'localizado' esa dirección, y asignar 8 a esa ubicación produciendo la siguiente memoria:

Dirección Contenido
0x8130 0x00000008
0x8134 0x00008130

Claramente, el acceso a a producirá el valor de 8 porque la instrucción anterior modificó el contenido de a por medio de el puntero ptr.

Matrices de C[editar]

En C, la indización de matrices se define formalmente en términos de punteros aritméticos, es decir, la especificación del lenguaje requiere que array[i] sea equivalente a *(array + i).[5] Así, en C, las matrices pueden ser consideradas como punteros a áreas de memoria consecutivas (sin espacios vacíos), [8] y la sintaxis para acceder a las matrices es idéntica a la cual se puede utilizar para desreferenciar punteros. Por ejemplo, una matriz array puede ser declarada y utilizada de la siguiente manera:

int array[5];      /* Declara 5 enteros contiguos */
int *ptr = array;  /* Las matrices pueden ser utilizadas como punteros */
ptr[0] = 1;        /* Los punteros se pueden indexar con la sintaxis de matrices */
*(array + 1) = 2;  /* Las matrices pueden ser dereferenciadas con sintaxis de puntero */
*(1 + array) = 3;  /* La adición del puntero es conmutativa */
2[array] = 4;      /* El operador subíndice es conmutativo */

Esto asigna un bloque de cinco enteros y nombres de la matriz de bloques, que actúa como un puntero al bloque. Otro uso común de los punteros es para que apunte a la memoria asignada dinámicamente desde malloc que devuelve un bloque consecutivo de memoria de no menos que el tamaño solicitado que se puede utilizar como una matriz.

Aunque la mayoría de los operadores sobre matrices y punteros sean equivalentes, es importante tener en cuenta que el operador sizeof será diferente. En este ejemplo, sizeof (array) evaluará a 5*sizeof(int) (el tamaño de la matriz), mientras que sizeof(ptr) evaluará sizeof (int*), el tamaño del propio puntero.

Los valores por defecto de una matriz se pueden declarar como:

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

Si se asume que array se encuentra en la memoria a partir de la dirección 0x1000 de una máquina little endian de 32 bits entonces la memoria contendrá lo siguiente (los valores se encuentran en hexadecimal, así como las direcciones):

0 1 2 3
1000 2 0 0 0
1004 4 0 0 0
1008 3 0 0 0
100C 1 0 0 0
1010 5 0 0 0

Aquí están representados cinco enteros: 2, 4, 3, 1 y 5. Estos cinco enteros ocupan 32 bits (4 bytes) cada uno con el byte menos significativo que se almacena primero (esto es una arquitectura de CPU little endian) y se almacenan de forma consecutiva comenzando en la dirección 0x1000.

La sintaxis de C con punteros es:

  • array significa 0x1000
  • array+1 significa 0x1004 (tener en cuenta que en realidad significa el "1" añade una vez el tamaño de un int (4 bytes) no literalmente "más uno")
  • *array significa desreferenciar los contenidos de array. Teniendo en cuenta el contenido como una dirección de memoria (0x1000), buscar el valor en esa ubicación (0x0002).
  • array[i] significa número de elemento i, base 0, de la matriz que se traduce en *(array + i)

El último ejemplo es cómo acceder a los contenidos del array. Descomponiéndolo:

  • array + i es la posición de memoria de la (i +1)-ésimo elemento de la matriz
  • *(array + i) toma esa dirección de memoria y elimina referencias a acceder al valor.

Por ejemplo array[3] es sinónimo de *(array+ 3), es decir, *(0x1000 + 3*sizeof (int)), que dice "eliminar la referencia al valor almacenado en 0x100C", en este caso 0x0001.

Lista vinculada en C[editar]

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  NULL
 
struct link {
    void        *datos;  /* datos de este vínculo */
    struct link *próximo;  /* siguiente enlace; EMPTY_LIST si no hay ninguno */
};

Nótese que esta definición puntero-recursivo es esencialmente la misma que la definición de referencia-recursiva del lenguaje de programación Haskell:

 data Link a = Nil
             | Cons a (Link a)

Nil es la lista vacía y Cons de un (Link a) es una cons cell de un tipo con otro enlace también de tipo a.

La definición de referencias, sin embargo, es de tipo comprobado y no utiliza los valores de señal potencialmente confusos. Por esta razón, en C las estructuras de datos normalmente se tratan a través de funciones contenedor, que son cuidadosamente verificadas para su corrección.

Pasaje por dirección utilizando punteros[editar]

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

/* se puede cambiar una copia de 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 a m. No se crea ninguna copia m de sí mismo */
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 ha cambiado dentro de la función, pero x sigue siendo 3 de aquí posteriormente
 
    /* pasar la dirección de x como argumento */
    passbyaddress(&x);
    // en realidad x fue cambiada por la función y ahora aquí es igual a 14
 
    return 0;
}

Asignación dinámica de memoria[editar]

Los punteros se utilizan para almacenar y administrar las direcciones de los bloques de memoria asignados dinámicamente. Dichos bloques se utilizan para almacenar objetos o conjuntos de objetos de datos. Los lenguajes más estructurados y orientados a objetos proporcionan un área de memoria, llamada el montón o tienda libre, de la que objetos dinámicamente asignados.

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

/* Item de inventario de partes */
struct Item {
    int         id;     /* Número de parte */
    char *      name;   /* Nombre de parte */
    float       cost;   /* Costo       */
};
 
/* Asignar e inicializar un nuevo objeto de elemento */
struct Item * make_item(const char *name) {
    struct Item * Item;
 
    /* Asignar un bloque de memoria para un nuevo objeto de elemento */
    Item = (struct Item *)malloc(sizeof(struct Item));
    if (Item == NULL)
        return NULL;
 
    /* Inicializa los miembros del nuevo elemento */
    memset(Item, 0, sizeof(struct Item));
    Item->id =   -1;
    Item->name = NULL;
    Item->cost = 0.0;
 
    /* Guarde una copia del nombre en el nuevo elemento */
    Item->name = (char *)malloc(strlen(name) + 1);
    if (Item->name == NULL) {
        free(Item);
        return NULL;
    }
    strcpy(Item->name, name);
 
    /* Devuelve el objeto de artículos recientemente creados */
    return Item;
}

El código siguiente muestra cómo se desasignan dinámicamente objetos de memoria, es decir, retorna al montón o tienda libre. La biblioteca C estándar proporciona la función free() para cancelar la asignación de un bloque de memoria previamente asignado y retornar de nuevo al montón.

/* Desasignar un objeto Item */
void destroy_item(struct Item *item) {
    /* Check for a null object pointer */
    if (item == NULL)
        return;
 
    /* Desasignar la cadena de nombre guardado en el Item */
    if (item->name != NULL) {
        free(item->name);
        item->name = NULL;
    }
 
    /* Desasignar el propio objeto Item */
    free(item);
}

Hardware asignado en memoria[editar]

En algunas arquitecturas de computación, los punteros pueden ser utilizados para manipular directamente de memoria o dispositivos asignados a la memoria.

La asignación direcciones a los punteros es una herramienta invaluable en la programación de microcontroladores. A continuación se muestra un simple ejemplo de declaración de un puntero de tipo int y la inicialización a una dirección hexadecimal en este ejemplo el constante 0x7FFF:

int *hardware_address = (int *)0x7FFF;

A mediados de los años 80, usar la BIOS para acceder a las capacidades de video de PC era lento. Las aplicaciones que se encontraban en pantalla intensiva normalmente se utiliza para acceder a la memoria de vídeo CGA directamente mediante colada las constantes hexadecimales 0xb8000 a un puntero a un array de 80 valores int de 16 bits sin signo. Cada valor consistía en un código ASCII en el byte bajo y un color en el byte alto. Por lo tanto, para poner la letra 'A' en la línea 5, columna 2 blanco sobre azul luminoso, uno podría escribir código como el siguiente:

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

Punteros con tipo y fundición[editar]

En muchos lenguajes, los punteros tienen la restricción adicional de que el objeto que apuntan tiene un tipo específico. Por ejemplo, un indicador puede ser declarado para apuntar a un número entero; será el lenguaje el que trate de evitar que el programador apunte a objetos que no fuesen números enteros, tales como números de coma flotante, eliminando algunas errores.

Por ejemplo, en C

int *money;
char *bags;

money sería un puntero entero y bags sería un puntero char. Lo siguiente daría una advertencia del compilador de "asignación desde un tipo de puntero" bajo GCC

bags = money;

porque money y bags fueron declarados con diferentes tipos. Para suprimir la advertencia del compilador, debe quedar explícita de que realmente se desea hacer la cesión por encasillamiento.

bags = (char *)money;

que dice emitir el puntero entero de money a un puntero char y asignarlo a bags.

Un proyecto de la norma C estándar de 2005 requiere que echando un puntero derivado de un tipo a uno de otro tipo debía mantener la corrección de alineación para ambos tipos (6.3.2.3 Punteros, par 7):[6]

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

En lenguajes que permiten la aritmética de punteros, esta de tiene en cuenta el tamaño del tipo. Por ejemplo, la adición de un número entero a un puntero produce otro puntero que apunta a una dirección que es superior en número de veces que el tamaño del tipo. Esto nos permite calcular fácilmente la dirección de elementos de una matriz de un tipo determinado, como se demostró en el ejemplo matrices C descripto arriba. Cuando un puntero de un tipo se convierte en otro tipo de un tamaño diferente, el programador debe esperar que el puntero aritmético se calcule de manera diferente. En C, por ejemplo, si la matriz money comienza a 0x2000 y sizeof (int) es 4 bytes mientras que sizeof (char) es de 1 byte, entonces (money+1) apuntará a 0x2004 pero (bags+1) apuntará a 0x2001. Entre otros riesgos de la fundición se incluyen la pérdida de datos, cuando los datos "anchos" se escribe en ubicaciones "estrechas" (por ejemplo, bags[0] = 65537;), se obtienen resultados inesperados cuando hay valores de desplazamiento de bits, y problemas de comparación, sobre todo entre valores con signo vs valores sin signo.

Aunque, en general, es imposible determinar en tiempo de compilación que arroja son seguros, algunos lenguajes almacenan el tipo de información en tiempo de ejecución que puede ser utilizado para confirmar que estos peligrosos moldes son válidos en tiempo de ejecución. Otros lenguajes simplemente aceptan una aproximación conservadora de moldes seguros, o ninguno en absoluto.

Haciendo más seguros a los punteros[editar]

Debido a que un puntero permite que un programa intente acceder a un objeto que no se puede definir, los punteros pueden ser la fuente 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 constructos diseñados para proporcionar algunas de las características útiles de punteros sin algunas de sus trampas, algunas veces también denominadas "punteros peligros".

Uno de los mayores problemas con los punteros es que al poderse manipular directamente como un número, se pueden hacer para apuntar a direcciones no utilizadas o a datos que se está utilizando para otros fines. Muchos lenguajes, incluyendo muchos lenguajes de programación funcionales y los últimos lenguajes imperativos como Java, reemplazan los punteros con un tipo más opaco de referencia, típicamente referido simplemente como referencia, que solo puede ser usado para referirse a los objetos y no manipula los números, previniendo este tipo de error. La indexación de matriz se trata como un caso especial.

Un puntero que no tenga ninguna dirección asignada a él se llama puntero salvaje. Cualquier intento de utilizar estos punteros no inicializados puede causar 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, violación de almacenamiento o rama natural (si se utiliza como un puntero de función o de dirección de rama).

En sistemas con asignación de memoria explícita, es posible crear un puntero de referencia colgante para des asignar la dirección de memoria que apunta dentro. Este tipo de puntero es peligroso y sutil, ya una región de memoria des asignada puede contener los mismos datos como lo hizo antes de que se cancela la asignación, pero puede ser reasignado a continuación y se sobrescriben con código ajeno, desconocido para el código anterior. Los lenguajes con recolector de basura previenen este tipo de error porque des afectación se realiza automáticamente cuando no hay más referencias en el alcance.

En sistemas con asignación de memoria explícita, es posible crear un puntero que cuelga des asignando la región de memoria a la que apunta. Este tipo de puntero es peligroso y sutil, ya que una región de memoria des asignada puede contener los mismos datos como sucedió antes de que se cancele la asignación pero, a continuación, puede ser reasignada y se sobrescriben con código ajeno, desconocido por el código anterior. Los lenguajes con recolección de basura previenen este tipo de error porque la des afectación se realiza automáticamente cuando no hay más referencias en el alcance.

Algunos lenguajes, como C++, soportan punteros inteligentes, que utilizan una forma simple de conteo de referencias para ayudar a la asignación de un registro de la memoria dinámica, además de actuar como una referencia. En la ausencia de ciclos de referencia, donde un objeto se refiere a sí mismo indirectamente mediante una secuencia de punteros inteligentes, éstos eliminan la posibilidad de punteros colgantes y las pérdidas de memoria. Las cadenas Delphi soportan, de forma nativa, recuento de referencias.

Puntero nulo[editar]

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 las condiciones tales como el final de una lista de longitud desconocida o el fracaso para llevar a cabo algún tipo de acción, lo que el uso de punteros nulos se puede comparar con los tipos que aceptan valores NULL y el valor de nada en un tipo de opción.

Frecuentemente, los punteros nulos se consideran similares a los valores nulos en las bases de datos relacionales, pero tienen una semántica algo diferente. En la mayoría de los lenguajes de programación, un puntero nulo significa "ningún valor", mientras que en una base de datos relacional, un valor nulo significa "valor desconocido". Esto conduce a importantes diferencias en la práctica: en la mayoría de los lenguajes de programación consideran iguales dos punteros nulos se, pero dos valores nulos en las bases de datos relacionales no lo son (no se sabe si son iguales, ya que representan valores desconocidos).

En algunos entornos de lenguaje de programación (al menos, por ejemplo, una implementación de Lisp propietaria[cita requerida]), el valor utilizado como puntero nulo (llamado nil en Lisp) puede, en realidad, ser un puntero a un bloque de datos internos de utilidad para la aplicación (pero no accesible explícitamente desde los programas de usuario), permitiendo así que el mismo registro sea utilizado como una constante útil y una forma rápida de acceder a partes internas de aplicación. Esto se conoce como vector nil (‘nulo’).

En C, dos punteros nulos de cualquier tipo están garantidos para comparar iguales tipo de datos[7] El macro NULL es una implementación definida por una constante de puntero NULL,[3] que en C99 se puede expresar portablemente como un valor entero 0 convertido implícita o explícitamente al tipo void*.[8]

Típicamente, desreferenciar el puntero NULL significa intentar leer o escribir en la memoria que no se asigna, esto desencadena un fallo de segmentación o violación de acceso. Esto puede representar en sí mismo, para el desarrollador, un fallo en el programa, o se transforma en una excepción que puede capturarse. Sin embargo, hay ciertas circunstancias en las que esto no es el caso. Por ejemplo, en modo en x86 real, la dirección 0000:0000 es legible y por lo general escribible, de ahí que la eliminación de referencias de puntero nulo sea una acción perfectamente válida pero, en general, no deseada que puede conducir a un comportamiento indefinido, pero no causa un crash en la aplicación. Además, tener en cuenta que hay ocasiones en que la desreferenciación NULL es intencional y bien definida, como por ejemplo el código del BIOS, escrito en C, para dispositivos x86 de 16 bits en modo real, puede escribir la IDT en la dirección física 0 de la máquina, desreferenciando al puntero a NULL para la escritura.

En C++, ya que el macro NULL fue heredado de C, tradicionalmente se prefere el literal entero para cero para representar una constante de puntero nulo.[9] Sin embargo, C++11 ha introducido una constante nullptr explícita que se utilizará en su lugar.

No se debe confundir un puntero nulo con un puntero no inicializado: Un puntero nulo está garantizado para comparar desigual a cualquier puntero que apunta a un objeto válido. Sin embargo, en función del idioma y la aplicación, un puntero no inicializado tiene, o bien un valor indeterminado (al azar o sin sentido), o un valor específico que no tiene por que ser necesariamente una especie de puntero nulo constante.

En 2009, C. A. R. Hoare declaró[10] [11] que en 1965 inventó la referencia nula como parte del lenguaje Algol W, aunque, desde 1959, NIL hubiera existido en Lisp. En esa referencia de 2009 Hoare describe su invención como un "error de millones de dólares":

A mi error yo lo llamo error de mil millones de dólares. Fue la invención, en 1965, de la referencia nula. En ese momento, yo estaba diseñando el primer sistema de tipo integral para las referencias en un lenguaje orientado a objetos (ALGOL W). Mi objetivo era asegurar que todo uso de referencias debe ser absolutamente seguras, con la comprobación realizada automáticamente por el compilador. Pero no pude resistir la tentación de poner en una referencia nula, simplemente porque era muy fácil de implementar. Esto ha dado lugar a innumerables errores, vulnerabilidades y fallos del sistema, que probablemente han causado mil millones de dólares de dolor y daños en los últimos cuarenta años.

Debido a que un puntero nulo no apunta a un objeto significativo, por lo general, (pero no siempre) intentar eliminar la referencia a un puntero nulo provoca un error en tiempo de ejecución o la inmediata caída del programa.

  • En C, no está definido el comportamiento de eliminación de referencias a un puntero nulo[12] Muchas implementaciones causan este tipo de código de lugar a que el programa se detenga con una violación de acceso, porque se elige la representación de puntero nulo para ser una dirección que no es asignada por el sistema para el almacenamiento de objetos. Sin embargo, este comportamiento no es universal.
  • En Java, acceder a una referencia nula desencadena una NullPointerException (NPE), que puede detectar el código de gestión de errores, pero en la práctica lo que se prefiere es asegurar que nunca ocurran tales excepciones.
  • En. NET, acceder a la referencia nula desencadena una excepción NullReferenceException. Aunque generalmente la captura de éstos se considera una mala práctica, se puede atrapar este tipo de excepción y manipularse por el programa.
  • En Objective-C, los mensajes se pueden enviar a un objeto nil (que es esencialmente un puntero nulo) sin causar que el programa se interrumpa; el mensaje es simplemente ignorado, y el valor de retorno (si lo hay) es nil o 0, dependiendo del tipo.[13]

En lenguajes con una arquitectura de etiquetado, posiblemente, un puntero nulo pueda ser reemplazado con una unión marcada que impone la manipulación explícita del caso excepcional, de hecho, un puntero nulo, posiblemente, pueda ser visto como un puntero etiquetado con una etiqueta computarizada.

Puntero autorelativo[editar]

El término puntero autorelativo puede referirse a un puntero cuyo valor se interpreta como un desplazamiento desde la dirección del propio puntero, por lo que, si una estructura de datos, M, tiene un elemento puntero autorelativo, p, que apunta a una porción M de sí mismo, entonces M puede ser reubicado en la memoria sin tener que actualizar el valor de p.[14] La patente citada también utiliza el término puntero autorelativo para significar la misma cosa. Sin embargo, el significado de ese término se ha utilizado en otras formas:

  • Es de uso frecuente significar un desplazamiento de la dirección de una estructura y no de la dirección de la propia puntero.[cita requerida]
  • Se ha utilizado para significar un puntero que contiene su propia dirección, que puede ser útil para la reconstrucción en cualquier región arbitraria de la memoria una colección de estructuras de datos que apuntan la una a la otra.[15]

Puntero base[editar]

Un puntero base es un puntero cuyo valor es un desplazamiento desde el valor de otro puntero. Esto puede ser usado para almacenar y cargar los bloques de datos, asignando la dirección de comienzo del bloque al puntero base.[16]

Indirección múltiple[editar]

En algunos lenguajes, un puntero puede hacer referencia a otro puntero, lo que requiere múltiples operaciones de des referenciación para llegar al valor original. Mientras que cada nivel de indirección puede añadir un costo de rendimiento, es a veces 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:

struct element
{
    struct element * next;
    int              value;
};
 
struct element * head = NULL;

Esta aplicación utiliza un puntero al primer elemento de la lista como un sustituto para la lista completa. Si se añade un nuevo valor al principio de la lista, debe cambiarse la cabecera para que apunte al nuevo elemento. Dado que argumentos C siempre se pasan por valor, utilizando direccionamiento indirecto doble se permite la inserción de implementarse correctamente, y tiene el efecto secundario deseable de eliminar el código de casos especiales para hacer frente a las inserciones en la parte delantera de la lista:

// Dada una lista ordenada en la * cabecera, insertar el elemento elemento en la primera
// lugar donde todos los elementos anteriores tienen menor o igual valor.
void insert(struct element **head, struct element *item)
{
    struct element ** p;  // p points to a pointer to an element
 
    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 la head, se actualizará el llamador head, correctamente a la dirección del nuevo item.

Ejemplo de uso de punteros en una estructura en C[editar]

El ejemplo que sigue es propio del lenguaje C/C++ y no es de aplicación en otros lenguajes de programación:

struct Elemento // Ejemplo de un nodo de lista doble enlazada
{
    int dato;
    struct Elemento *siguiente; // El '*' es el operador de indirección, y es el usado para declarar punteros
    struct Elemento *anterior;
};

Para acceder a los atributos como punteros de una estructura que va a ser tratada como tal, se debe desreferenciar el puntero y acceder a sus miembros como se haría con una variable normal, o usar directamente el operador: ->. De tal modo que:

Elemento *elem;
Elemento sig1 = (*elem).siguiente;
Elemento sig2 = elem->siguiente;
/* Se cumple que: sig1==sig2 */

Los paréntesis en este ejemplo son necesarios, pues el operador '*' es el que menor prioridad de operaciones tiene asignada (por lo que se haría *(elem.siguiente), lo que es incorrecto, pues trataría acceder a un campo de una dirección de memoria, y no de una estructura. Esto es un error sintáctico, en tiempo de compilación).

Otro ejemplo en C++: Se presenta una función que no devuelve ningún valor, esta función llamada "swap" tiene como parámetros dos punteros del tipo int. Así, cuando sea llamada desde alguna parte del programa, recibirá las direcciones de dos variables; luego accederá a dichas variables gracias al operador de indirección (* precediendo al identificador del puntero) y podrá intercambiar sus valores residentes.

void swap(int *x, int *y)
{
    int temp;
    temp = *x;  // copia el valor apuntado por x a temp
    *x = *y;    // copia el valor apuntado por y en la ubicación del puntero x
    *y = temp;  // copia el valor de temp en la ubicación apuntada por y
}

Ejemplo en C#

//Suma de dos números enteros
private unsafe int Suma(int* a, int* b)
{
    return *a + *b;
}
 
// Su uso (El método llamador también debe tener la palabra clave 'unsafe'):
// int x, y;
// int *ptr1 = &x;
// int *ptr2 = &y;
// Suma(ptr1, ptr2);

Véase también[editar]

Referencias[editar]

  1. ISO/IEC 9899, cláusua 6.7.5.1, párrafo 1.
  2. ISO/IEC 9899, cláusula 6.7.8, párrafo 10.
  3. a b ISO/IEC 9899, cláusula 7.17, párrafo 3: NULL... que expande a una implementación definida como puntero constante nulo...
  4. ISO/IEC 9899, cláusula 6.5.3.2, párrafo 4, footnote 87: Si un valor no válido ha sido asignada al puntero, el comportamiento del operador unario * es indefinido ... Entre los valores válidos para desreferenciar un puntero por el operador unario * son un puntero nulo...
  5. Plauger, P J; Brodie, Jim (1992). ANSI and ISO Standard C Programmer's Reference. Redmond, WA: Microsoft Press. pp. 108, 51. ISBN 1-55615-359-7. «Un tipo de matriz no contiene agujeros adicionales porque todos los otros tipos de paquetes se hermetizan cuando están compuestos en matrices [en página 51]» 
  6. WG14 N1124, C – Approved standards: ISO/IEC 9899 – Programming languages – C, 6 de mayo de 2005.
  7. ISO/IEC 9899, cláusula 6.3.2.3, párrafo 4.
  8. ISO/IEC 9899, cláusula 6.3.2.3, párrafo 3.
  9. Stroustrup, Bjarne (marzo de 2001). «Chapter 5: Pointers, Arrays, and Structures: 5.1.1: Zero». The C++ Programming Language (14th printing of 3ra edición). Estados Unidos y Canadá: Addison–Wesley. p. 88. ISBN 0-201-88954-4. «En C, ha sido popular definir un macro NULL para representar el puntero cero. Debido a que en C++ es más estricta la comprobación de tipo, el uso del 0 plano, en lugar de cualquier macro NULL sugerido, conduce a un menor número de problemas. Si se necesita definir NULL. utilizar : const int NULL = 0; El calificador const (§ 5.4) previene redefinición accidental de NULL y se asegura de que NULL se puede utilizar cuando se requiere una constante.» 
  10. Tony Hoare (2009). «Null References: The Billion Dollar Mistake».
  11. Tony Hoare (25 de agosto de 2009). «Null References: The Billion Dollar Mistake». InfoQ.com.
  12. ISO/IEC 9899, cláusula 6.5.3.2, párrafo 4.
  13. The Objective-C 2.0 Programming Language, sección "Sending Messages to nil".
  14. Plantilla:Cita patente
  15. Plantilla:Cita patente
  16. Based Pointers

Enlaces externos[editar]