¿Qué significa "desreferenciar" un puntero?

Incluya un ejemplo con la explicación.

preguntado el 10 de febrero de 11 a las 06:02

int *p; definiría un puntero a un número entero, y *p desreferenciaría ese puntero, lo que significa que en realidad recuperaría los datos a los que apunta p. -

Diversión del puntero de Binky (cslibrary.stanford.edu/104) es un GRAN video sobre sugerencias que podrían aclarar las cosas. @ Erik- Estás genial por poner el enlace de la biblioteca de Stanford CS. Hay tantas golosinas allí ... -

La respuesta de Harry es lo opuesto a útil aquí. -

6 Respuestas

Revisión de la terminología básica

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

  • ¿Qué pasó con 0 y el primer byte? Bueno, llegaremos a eso más tarde - mira punteros nulos debajo.
  • 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 acerca de las direcciones de memoria y por qué probablemente no es necesario que las sepa" al final de esta respuesta.

Cuando desee 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, entonces desreferencia el puntero.

Los diferentes lenguajes de computadora tienen diferentes notaciones para decirle al compilador o intérprete que ahora estás interesado en el valor (actual) del objeto apuntado; a continuación, me centro en C y C ++.

Un escenario puntero

Considere en C, dado un puntero como p abajo...

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 ASCIIZAR.

Por ejemplo, si el literal de cadena estaba en la dirección 0x1000 y p un 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 un nombre / identificador de variable para la dirección 0x1000, pero podemos referirnos indirectamente al literal de cadena usando un puntero que almacena su dirección: p.

Desreferenciar el puntero

Para referirse a los personajes p señala, desreferenciamos p usando 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, eliminándolos 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 tiempo de 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 usando 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 algo de información sobre el tipo de datos que se está apuntando; 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 la memoria asignada dinámicamente

A veces no sabe cuánta memoria necesitará hasta que su programa se esté ejecutando y vea qué datos se le arrojan ... luego puede 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 se realiza normalmente con el new operador y desasignació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;

Vea también Punteros inteligentes de C ++ debajo.

Perdiendo y filtrando direcciones

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() or delete para evitar 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 de C ++

En C ++, se recomienda utilizar puntero inteligente objetos para almacenar y administrar los punteros, desasignándolos automáticamente cuando se ejecutan los destructores de los punteros inteligentes. Dado que C ++ 11, la biblioteca estándar proporciona dos, unique_ptr para cuando hay un solo 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_ptr para la propiedad de acciones (usando recuento de referencia) ...

{
    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

C ª, NULL y 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 debe desreferenciarse ni usarse en aritmética de punteros. 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 ++, al igual que los tipos numéricos incorporados no necesariamente por defecto 0, ni bools a false, los punteros no siempre se establecen en NULL. Todos estos se establecen en 0 / false / NULL cuando están static variables o (solo C ++) variables miembro directas o indirectas de objetos estáticos o sus bases, o se someten a inicialización cero (p. ej. new T(); y new T(x, y, z); realizar una inicialización cero en los miembros de T, incluidos los punteros, mientras que new T; no).

Además, cuando asigna 0, NULL y nullptr para un puntero, los bits en el puntero no se restablecen necesariamente todos: el puntero puede no contener "0" en el nivel de hardware, o hacer referencia a la dirección 0 en su espacio de direcciones virtuales. El compilador puede almacenar algo más allí si tiene una razón para hacerlo, pero haga lo que haga, si viene y compara el puntero con 0, NULL, nullptr u otro puntero al que se le haya asignado alguno de esos, la comparación debe funcionar 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 acerca de las direcciones de memoria y por qué probablemente no las necesite

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

El caso simple es donde 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 algún área de memoria específica, que la CPU puede seleccionar en función de los registros de "segmento" de la CPU o alguna forma de identificación de segmento codificada en el patrón de bits, y / o mirando en diferentes lugares dependiendo de la instrucciones de código de máquina utilizando la dirección.

Por ejemplo, una int* correctamente inicializado para apuntar a un int variable podría - después de convertir a un float* - acceder a la memoria en la memoria "GPU" bastante distinta de la memoria en la que int La variable es, luego una vez convertida y utilizada como un puntero de función, podría apuntar a códigos de operación de máquina de retención de memoria distintos para el programa (con el valor numérico de la int* efectivamente un puntero no válido 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 (siempre y cuando la variable no se destruya / desasigne mientras tanto) y es 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 punteros para moverse a cualquier otro lugar de la matriz, o incluso para formar una dirección más allá del final de la matriz que sea legal para comparar con otros punteros a elementos. en la matriz (o que de manera similar se hayan movido mediante aritmética de puntero al mismo valor de uno más allá del final); 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 indicaciones y "simplemente funcionarán" dentro del rango de direcciones que tenga sentido para ellas.

  • Los intentos de mover punteros legales más allá de estos límites, o de lanzar números arbitrarios a punteros, o usar punteros lanzados a tipos no relacionados, generalmente tienen comportamiento indefinido, por lo que debe evitarse en bibliotecas y aplicaciones de nivel superior, pero el código para sistemas operativos, controladores de dispositivos, etc. puede necesitar depender de un comportamiento no definido por el estándar C o C ++, que sin embargo está bien definido por su implementación o hardware específico.

Respondido el 05 de junio de 20 a las 23:06

@Pacerier: de 6.5.2.1/2 en el borrador del Estándar C N1570 (primero lo 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 que un compilador no los convierta inmediatamente en representaciones idénticas en una etapa inicial de compilación, aplicando las mismas optimizaciones después de eso, pero no veo cómo alguien puede probar definitivamente que el código sería idéntico sin examinar todos los compiladores jamás escritos. - tony delroy

@Honey: el valor 1000 hexadecimal es demasiado grande para codificarlo en un solo byte (8 bits) de memoria: solo puede almacenar números sin firmar del 0 al 255 en un byte. Por lo tanto, no puede almacenar 1000 hexadecimales en "solo" la dirección 2000. En su lugar, 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 p es solo 2000: si tuvieras otro puntero para p tendría que almacenar 2000 en sus cuatro u ocho bytes. ¡Espero que ayude! Salud. - tony delroy

@TonyDelroy: Si un sindicato u contiene una matriz arr, tanto gcc como clang reconocerán que el valor l u.arr[i] podría acceder al mismo almacenamiento que otros miembros del sindicato, pero no reconocerá que lvalue *(u.arr+i) podría hacerlo. No estoy seguro de si los autores de esos compiladores piensan que el último invoca a UB, o que el primero invoca a UB, pero deberían procesarlo de manera útil de todos modos, pero claramente ven las dos expresiones como diferentes. - Super gato

Rara vez he visto punteros y su uso dentro de C / C ++ explicado de manera tan concisa y simple. - kayleefrye_ondeck

@TonyDelroy: Lo que se necesita para la seguridad y la optimización no es tanto un operador de "transmisión de bits", sino un tipo de "puntero restringido" que durante su vida útil requiere que se acceda exclusivamente a todas las partes de un objeto a las que se accede mediante un puntero restringido. a través de él, y cuyo constructor podría tomar un puntero de cualquier tipo y hacer que los accesos realizados a través del puntero restringido sean tratados como accesos al tipo original. La mayor parte del código que necesita usar juegos de palabras con tipos sería susceptible de tal construcción, y permitiría muchas optimizaciones útiles que irían más allá de TBAA. - Super gato

Desreferenciar un puntero significa obtener el valor que está almacenado en la ubicación de memoria apuntada por el puntero. El operador * se utiliza para hacer esto, y se denomina 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.

Respondido el 29 de enero de 14 a las 16:01

Un puntero no apunta a un propuesta de, apunta a un objeto. - Keith Thompson

@KeithThompson Un puntero no apunta a un objeto, apunta a una dirección de memoria, donde se encuentra un objeto (tal vez una primitiva). - mg30rg

@ mg30rg: No estoy seguro de qué distinción está haciendo. Un valor de puntero is 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

@KeithThompson Apenas estaba señalando, que en realidad no agregaste valor a la respuesta, solo estabas puntiagudo 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 impulsado por OOP puede ser engañosa, porque se puede interpretar como "instancia de clase" (sí, no sabía que la pregunta está etiquetada como [C] y no [C ++]), y usé la palabra "primitivo" como en el opuesto de "copmlex" (estructura de datos como una estructura o clase). - mg30rg

Permítanme agregar a esta respuesta que el operador de subíndice de matriz [] también elimina la referencia de un puntero (a[b] se define para significar *(a + b)). - cmaster - reinstalar a monica

Un puntero es una "referencia" a un valor ... al igual que el número de clasificación de una biblioteca es una referencia a un libro. "Desreferenciar" el número de clasificación es revisar físicamente y recuperar 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 se preparan para investigar la causa de que una persona encuentre un libro que no está allí.

Respondido el 10 de diciembre de 13 a las 18:12

En palabras simples, desreferenciar significa acceder al valor desde una determinada ubicación de memoria a la que apunta ese puntero.

Respondido 11 Feb 17, 19:02

Código y explicación de Conceptos básicos del puntero:

La operación de eliminación de referencias comienza en el puntero y sigue su flecha para acceder a su puntero. El objetivo puede ser observar el estado de las puntas o cambiar el estado de las puntas. La operación de eliminación de referencia en un puntero solo funciona si el puntero tiene un puntero; el puntero debe asignarse y el puntero debe configurarse para que apunte a él. El error más común en el código de puntero es olvidar 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 eliminación de referencia fallida. En Java, la desreferenciación incorrecta será marcada cortésmente por el sistema de tiempo de ejecución. En lenguajes compilados como C, C ++ y Pascal, la desreferencia incorrecta a veces falla y otras veces corrompe la memoria de alguna manera sutil y aleatoria. Los errores de puntero en lenguajes compilados pueden ser difíciles de rastrear por esta razón.

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   
}

Respondido 10 Feb 11, 09:02

En realidad, debe asignar memoria para donde se supone que apunte x. Tu ejemplo tiene un comportamiento indefinido. - Peyman

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

Opera 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 eliminar la referencia del 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 en realidad de la máquina los datos mediante un puntero no válido. Sin embargo, desafortunadamente, de acuerdo con el estándar, eliminar la referencia a 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.

Respondido 11 Feb 17, 19:02

Bueno, desreferenciaste un puntero NULL, lo que provocaría un error de segmentación. - Arjun Gaur

además de eso, buscó 'operador de eliminación de referencias' y no 'eliminación de referencias de un puntero', lo que en realidad significa obtener el valor / acceder a un valor en una ubicación de memoria a la que apunta un puntero. - Arjun Gaur

¿Has probado? Yo hice. Lo siguiente no se bloquea: `#include int main () {char * p = NULL; *pag; return 0; } `- stsp

@stsp Lo hace porque el código no falla ahora no significa que no lo hará en el futuro, o en algún otro sistema. - usuario146043

*p; provoca un comportamiento indefinido. Aunque tienes razón en que la desreferenciación no accede al valor per se, el código *p; acceder al valor. - MM

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.