¿Qué significa "desreferenciar" un puntero?

541

Incluya un ejemplo con la explicación.

asir
fuente
esto puede ayudarlo: stackoverflow.com/questions/2795575/…
Harry Joy
24
int *p;definiría un puntero a un entero y *pdesreferenciaría ese puntero, lo que significa que realmente recuperaría los datos a los que apunta p.
Peyman
44
Binky's Pointer Fun ( cslibrary.stanford.edu/104 ) es un GRAN video sobre punteros que pueden aclarar las cosas. @ Erik- Eres genial por poner el enlace de la Biblioteca CS de Stanford Hay tantas cosas buenas allí ...
templatetypedef
66
La respuesta de Harry es lo contrario de útil aquí.
Jim Balter

Respuestas:

731

Revisando la terminología básica

Por lo general, es lo suficientemente bueno, a menos que esté programando el ensamblaje, para imaginar un puntero que contenga una dirección de memoria numérica, con 1 que se refiere al segundo byte en la memoria del proceso, 2 el tercero, 3 el cuarto y así sucesivamente ...

  • ¿Qué pasó con 0 y el primer byte? Bueno, llegaremos a eso más tarde; vea los punteros nulos a continuación.
  • Para obtener una definición más precisa de lo que almacenan los punteros y cómo se relacionan la memoria y las direcciones, consulte "Más información sobre las direcciones de memoria y por qué probablemente no necesite saber" al final de esta respuesta.

Cuando desea acceder a los datos / valores en la memoria a los que apunta el puntero (el contenido de la dirección con ese índice numérico), desreferencia el puntero.

Diferentes lenguajes de computadora tienen diferentes anotaciones para decirle al compilador o al intérprete que ahora está interesado en el valor (actual) del objeto señalado: me centro más adelante en C y C ++.

Un escenario de puntero

Considere en C, dado un puntero como el psiguiente ...

const char* p = "abc";

... cuatro bytes con los valores numéricos utilizados para codificar las letras 'a', 'b', 'c' y un byte 0 para indicar el final de los datos textuales, se almacenan en algún lugar de la memoria y la dirección numérica de ese los datos se almacenan en p. De esta manera, C codifica el texto en la memoria se conoce como ASCIIZ .

Por ejemplo, si el literal de cadena está en la dirección 0x1000 y pun puntero de 32 bits en 0x2000, el contenido de la memoria sería:

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

Tenga en cuenta que no hay ningún nombre de variable / identificador dirección 0x1000, pero puede referirse indirectamente a la cadena literal usando un puntero almacenar su dirección: p.

Desreferenciar el puntero

Para referirnos a los ppuntos de caracteres , desreferenciamos pusando una de estas notaciones (nuevamente, para C):

assert(*p == 'a');  // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
                     // p and 1 times the size of the things to which p points:
                     // In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b');  // Another notation for p[1]

También puede mover punteros a través de los datos apuntados, desreferenciarlos a medida que avanza:

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

Si tiene algunos datos en los que se puede escribir, puede hacer cosas como esta:

int x = 2;
int* p_x = &x;  // Put the address of the x variable into the pointer p_x
*p_x = 4;       // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4

Arriba, debe haber sabido en el momento de la compilación que necesitaría una variable llamada x, y el código le pide al compilador que organice dónde debe almacenarse, asegurando que la dirección estará disponible a través de &x.

Desreferenciar y acceder a un miembro de datos de estructura

En C, si tiene una variable que es un puntero a una estructura con miembros de datos, puede acceder a esos miembros utilizando el ->operador de desreferenciación:

typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159;  // Dereference and access data member x.d_
(*p).d_ *= -1;    // Another equivalent notation for accessing x.d_

Tipos de datos de varios bytes

Para usar un puntero, un programa de computadora también necesita una idea del tipo de datos al que se apunta: si ese tipo de datos necesita más de un byte para representar, entonces el puntero normalmente apunta al byte con el número más bajo en los datos.

Entonces, mirando un ejemplo un poco más complejo:

double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3);  // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4);  // Actually looks at bytes from address p + 1 * sizeof(double)
                       // (sizeof(double) is almost always eight bytes)
++p;                   // Advance p by sizeof(double)
assert(*p == 13.4);    // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8;       // Change sizes[3] from 19.4 to 29.8
                       // Note earlier ++p and + 2 here => sizes[3]

Punteros a memoria asignada dinámicamente

A veces no sabes cuánta memoria necesitarás hasta que tu programa se esté ejecutando y ve qué datos se le arrojan ... entonces puedes asignar memoria dinámicamente usando malloc. Es una práctica común almacenar la dirección en un puntero ...

int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10;            // Dereference the pointer to the memory, then write a value in
fn(*p);             // Call a function, passing it the value at address p
(*p) += 3;          // Change the value, adding 3 to it
free(p);            // Release the memory back to the heap allocation library

En C ++, la asignación de memoria normalmente se realiza con el newoperador, y la asignación con delete:

int* p = new int(10); // Memory for one int with initial value 10
delete p;

p = new int[10];      // Memory for ten ints with unspecified initial value
delete[] p;

p = new int[10]();    // Memory for ten ints that are value initialised (to 0)
delete[] p;

Consulte también los punteros inteligentes de C ++ a continuación.

Direcciones perdidas y con fugas

A menudo, un puntero puede ser la única indicación de dónde existen algunos datos o búfer en la memoria. Si se necesita el uso continuo de esos datos / búfer, o la capacidad de llamar free()o deleteevitar fugas de memoria, entonces el programador debe operar en una copia del puntero ...

const char* p = asprintf("name: %s", name);  // Common but non-Standard printf-on-heap

// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
    if (!isprint(*q))
        *q = '_';

printf("%s\n", p); // Only q was modified
free(p);

... o orquestar cuidadosamente la reversión de cualquier cambio ...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...
free(p);

Punteros inteligentes C ++

En C ++, es una buena práctica usar objetos de puntero inteligente para almacenar y administrar los punteros, desagregándolos automáticamente cuando se ejecutan los destructores de punteros inteligentes. Desde C ++ 11, la Biblioteca estándar proporciona dos, unique_ptrpara cuando hay un único propietario para un objeto asignado ...

{
    std::unique_ptr<T> p{new T(42, "meaning")};
    call_a_function(p);
    // The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete

... y shared_ptrpara la propiedad de acciones (utilizando el recuento de referencias ) ...

{
    auto p = std::make_shared<T>(3.14, "pi");
    number_storage1.may_add(p); // Might copy p into its container
    number_storage2.may_add(p); // Might copy p into its container    } // p's destructor will only delete the T if neither may_add copied it

Punteros nulos

En C, NULLy 0- y adicionalmente en C ++ nullptr- se puede usar para indicar que un puntero no contiene actualmente la dirección de memoria de una variable, y no se debe desreferenciar o usar en la aritmética del puntero. Por ejemplo:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
    switch (c) {
      case f: p_filename = optarg; break;
    }
if (p_filename)  // Only NULL converts to false
    ...   // Only get here if -f flag specified

En C y C ++, del mismo modo que los tipos numéricos incorporados no necesariamente predeterminan 0, ni boolsa false, los punteros no siempre se establecen en NULL. Todos estos se establecen en 0 / falso / NULO cuando son staticvariables o (solo C ++) variables miembro directas o indirectas de objetos estáticos o sus bases, o se someten a cero inicialización (por ejemplo, new T();y new T(x, y, z);realizan cero inicialización en miembros de T, incluidos punteros, mientras que new T;no).

Además, cuando asigna 0, NULLy nullptra un puntero, los bits en el puntero no se reinician necesariamente: el puntero puede no contener "0" en el nivel de hardware o hacer referencia a la dirección 0 en su espacio de dirección virtual. El compilador está permitido almacenar algo más allí si tiene razones para, pero haga lo que haga - si se llega a lo largo y comparar el puntero 0, NULL, nullptru otro puntero que se asignó ninguna de ellas, el trabajo de comparación obligada como se esperaba. Entonces, debajo del código fuente en el nivel del compilador, "NULL" es potencialmente un poco "mágico" en los lenguajes C y C ++ ...

Más información sobre las direcciones de memoria y por qué probablemente no necesite saber

Más estrictamente, los punteros inicializados almacenan un patrón de bits que identifica una NULLo una dirección de memoria (a menudo virtual ).

El caso simple es cuando se trata de un desplazamiento numérico en todo el espacio de direcciones virtuales del proceso; en casos más complejos, el puntero puede ser relativo a un área de memoria específica, que la CPU puede seleccionar en función de los registros de "segmento" de la CPU o de alguna forma de identificación de segmento codificada en el patrón de bits, y / o buscando en diferentes lugares dependiendo de instrucciones del código de máquina usando la dirección.

Por ejemplo, una int*inicialización adecuada para apuntar a una intvariable podría, después de convertir a a float*, la memoria de acceso en la memoria "GPU" bastante distinta de la memoria donde está la intvariable, luego, una vez convertida y utilizada como puntero de función, podría apuntar a más distintos códigos opcionales de la máquina de almacenamiento de memoria para el programa (con el valor numérico del int*puntero efectivamente no aleatorio dentro de estas otras regiones de memoria).

Los lenguajes de programación 3GL como C y C ++ tienden a ocultar esta complejidad, de modo que:

  • Si el compilador le da un puntero a una variable o función, puede desreferenciarla libremente (mientras la variable no se destruya / desasigne mientras tanto) y es el problema del compilador si, por ejemplo, un registro de segmento de CPU en particular necesita ser restaurado de antemano, o un instrucción de código de máquina distinta utilizada

  • Si obtiene un puntero a un elemento en una matriz, puede usar la aritmética de puntero para moverse a cualquier otro lugar de la matriz, o incluso para formar una dirección un paso al final de la matriz que es legal para comparar con otros punteros a elementos en la matriz (o que hayan sido movidos de manera similar por la aritmética del puntero al mismo valor de pasado); nuevamente en C y C ++, depende del compilador asegurarse de que esto "simplemente funcione"

  • Las funciones específicas del sistema operativo, por ejemplo, el mapeo de memoria compartida, pueden darle punteros, y "simplemente funcionarán" dentro del rango de direcciones que tenga sentido para ellos.

  • Los intentos de mover punteros legales más allá de estos límites, o de emitir números arbitrarios a punteros, o utilizar punteros emitidos a tipos no relacionados, generalmente tienen un comportamiento indefinido , por lo que se debe evitar en bibliotecas y aplicaciones de nivel superior, pero codifique para sistemas operativos, controladores de dispositivos, etc. Es posible que deba basarse en un comportamiento que el estándar C o C ++ no haya definido, que, sin embargo, está bien definido por su implementación o hardware específicos.

Tony Delroy
fuente
es p[1] e *(p + 1) idéntico ? Es decir, ¿ genera p[1] y *(p + 1)genera las mismas instrucciones?
Pacerier
2
@Pacerier: de 6.5.2.1/2 en el borrador del Estándar N1570 C (primero que encontré en línea) "La definición del operador de subíndice [] es que E1 [E2] es idéntico a (* ((E1) + (E2)) ) ". - No puedo imaginar ninguna razón por la cual un compilador no los convierta inmediatamente en representaciones idénticas en una etapa temprana de compilación, aplicando las mismas optimizaciones después de eso, pero no veo cómo alguien pueda probar definitivamente que el código sería idéntico sin examinar cada compilador jamás escrito.
Tony Delroy
3
@Honey: el valor 1000 hexadecimal es demasiado grande para codificar en un solo byte (8 bits) de memoria: solo puede almacenar números sin signo de 0 a 255 en un byte. Por lo tanto, no puede almacenar 1000 hexadecimales en "solo" la dirección 2000. En cambio, un sistema de 32 bits usaría 32 bits, que son cuatro bytes, con direcciones de 2000 a 2003. Un sistema de 64 bits usaría 64 bits - 8 bytes - de 2000 a 2007. De cualquier manera, la dirección base de pes solo 2000: si tuviera otro puntero p, tendría que almacenar 2000 en sus cuatro u ocho bytes. ¡Espero que ayude! Salud.
Tony Delroy
1
@TonyDelroy: si una unión ucontiene una matriz arr, tanto gcc como clang reconocerán que lvalue u.arr[i]podría acceder al mismo almacenamiento que otros miembros de la unión, pero no reconocerá que lvalue *(u.arr+i)podría hacerlo. No estoy seguro de si los autores de esos compiladores piensan que este último invoca a UB, o que el primero invoca a UB, pero de todos modos deberían procesarlo de manera útil, pero claramente ven las dos expresiones como diferentes.
supercat
3
Raramente he visto punteros y su uso dentro de C / C ++ de manera tan concisa y simple.
kayleeFrye_onDeck
102

Anular la referencia a un puntero significa obtener el valor que está almacenado en la ubicación de memoria señalada por el puntero. El operador * se usa para hacer esto, y se llama operador de desreferenciación.

int a = 10;
int* ptr = &a;

printf("%d", *ptr); // With *ptr I'm dereferencing the pointer. 
                    // Which means, I am asking the value pointed at by the pointer.
                    // ptr is pointing to the location in memory of the variable a.
                    // In a's location, we have 10. So, dereferencing gives this value.

// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.

 *ptr = 20;         // Now a's content is no longer 10, and has been modified to 20.
Mahesh
fuente
15
Un puntero no apunta a un valor , apunta a un objeto .
Keith Thompson
52
@KeithThompson Un puntero no apunta a un objeto, apunta a una dirección de memoria, donde se encuentra un objeto (quizás un primitivo).
mg30rg
44
@ mg30rg: no estoy seguro de qué distinción estás haciendo. Un valor de puntero es una dirección. Un objeto, por definición, es una "región de almacenamiento de datos en el entorno de ejecución, cuyo contenido puede representar valores". ¿Y qué quieres decir con "primitivo"? El estándar C no usa ese término.
Keith Thompson
66
@KeithThompson Apenas estaba señalando, que en realidad no agregaste valor a la respuesta, solo estabas jugando con la terminología (y también lo hiciste mal). El valor del puntero seguramente es una dirección, así es como "apunta" a una dirección de memoria. La palabra "objeto" en nuestro mundo orientado a OOP puede ser engañosa, porque puede interpretarse como "instancia de clase" (sí, no sabía que la pregunta está etiquetada [C] y no [C ++]), y usé la palabra "primitivo" como en el opuesto de "copmlex" (estructura de datos como una estructura o clase).
mg30rg
3
Permítanme agregar a esta respuesta que el operador de subíndice de matriz []también desreferencia un puntero ( a[b]se define como medio *(a + b)).
cmaster - restablecer monica
20

Un puntero es una "referencia" a un valor ... al igual que un número de llamada de la biblioteca es una referencia a un libro. "Desreferenciando" el número de la llamada está pasando físicamente y recuperando ese libro.

int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;

// The * causes pA to DEREFERENCE...  `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4.. 

Si el libro no está allí, el bibliotecario comienza a gritar, cierra la biblioteca y un par de personas están preparadas para investigar la causa de que una persona vaya a encontrar un libro que no está allí.

bobobobo
fuente
17

En palabras simples, la desreferenciación significa acceder al valor desde una determinada ubicación de memoria contra la cual apunta el puntero.

Fahad Naeem
fuente
7

Código y explicación de Pointer Basics :

La operación de desreferencia comienza en el puntero y sigue su flecha para acceder a su puntero. El objetivo puede ser mirar el estado de la punta o cambiar el estado de la punta. La operación de desreferencia en un puntero solo funciona si el puntero tiene un puntero: el puntero debe estar asignado y el puntero debe estar configurado para que apunte a él. El error más común en el código del puntero es olvidarse de configurar el puntero. El bloqueo de tiempo de ejecución más común debido a ese error en el código es una operación de desreferencia fallida. En Java, la desreferencia incorrecta será marcada cortésmente por el sistema de tiempo de ejecución. En lenguajes compilados como C, C ++ y Pascal, la desreferencia incorrecta algunas veces fallará y otras veces corromperá la memoria de alguna manera sutil y aleatoria.

void main() {   
    int*    x;  // Allocate the pointer x
    x = malloc(sizeof(int));    // Allocate an int pointee,
                            // and set x to point to it
    *x = 42;    // Dereference x to store 42 in its pointee   
}
atp
fuente
En realidad, debe asignar memoria a donde se supone que x debe apuntar. Su ejemplo tiene un comportamiento indefinido.
Peyman
3

Creo que todas las respuestas anteriores son incorrectas, ya que afirman que desreferenciar significa acceder al valor real. Wikipedia da la definición correcta en su lugar: https://en.wikipedia.org/wiki/Dereference_operator

Funciona en una variable de puntero y devuelve un valor l equivalente al valor en la dirección del puntero. Esto se llama "desreferenciar" el puntero.

Dicho esto, podemos desreferenciar el puntero sin tener que acceder al valor al que apunta. Por ejemplo:

char *p = NULL;
*p;

Desreferenciamos el puntero NULL sin acceder a su valor. O podríamos hacer:

p1 = &(*p);
sz = sizeof(*p);

Nuevamente, desreferenciar, pero nunca acceder al valor. Dicho código NO se bloqueará: el bloqueo ocurre cuando realmente accede a los datos mediante un puntero no válido. Sin embargo, desafortunadamente, según el estándar, desreferenciar un puntero no válido es un comportamiento indefinido (con algunas excepciones), incluso si no intenta tocar los datos reales.

En resumen: desreferenciar el puntero significa aplicarle el operador de desreferencia. Ese operador solo devuelve un valor l para su uso futuro.

stsp
fuente
bueno, desreferenciado un puntero NULL, eso conduciría a una falla de segmentación.
arjun gaur
además de eso, buscó 'operador de desreferenciación' y no 'desreferenciación de un puntero', lo que en realidad significa obtener el valor / acceder a un valor en una ubicación de memoria señalada por un puntero.
arjun gaur
¿Has probado? Yo hice. Lo siguiente no se bloquea: `#include <stdlib.h> int main () {char * p = NULL; *pags; devuelve 0; } `
stsp
1
@stsp El hecho de que el código no se bloquee ahora no significa que no lo hará en el futuro o en algún otro sistema.
1
*p;Causa un comportamiento indefinido. Aunque tiene razón en que la desreferenciación no accede al valor per se , el código *p; accede al valor.
MM