c++ - pointer - dereferenced traductor
¿Qué significa "desreferenciación" de un puntero? (6)
Repasando la terminología básica.
Generalmente es lo suficientemente bueno, a menos que esté programando el ensamblaje, para prever un puntero que contenga una dirección de memoria numérica, con 1 que se refiera 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 - vea los punteros nulos a continuación.
- Para obtener una definición más precisa de qué 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é es probable que no necesite saber" .
Cuando desee acceder a los datos / valores en la memoria a la que apunta el puntero (el contenido de la dirección con ese índice numérico), entonces debe eliminar la referencia del puntero.
Los diferentes lenguajes informáticos tienen diferentes notaciones para decirle al compilador o intérprete que ahora está interesado en el valor apuntado: me enfoco a continuación en C y C ++.
Un escenario de puntero
Considerar 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 esos Los datos se almacenan en la p
.
Por ejemplo, si la cadena literal estuviera en la dirección 0x1000 y en 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 a la cadena literal usando un puntero que almacena su dirección: p
.
Desreferenciación del puntero
Para referirnos a los caracteres a los que apunta p
, desreferimos 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 los punteros a través de los datos apuntados a, eliminándolos de referencia 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, entonces 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
Anteriormente, 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
.
Desreferenciación y acceso 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 multi-byte
Para usar un puntero, un programa de computadora también necesita cierta información sobre el 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 de 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)
assert(++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 vea qué datos se le arrojan ... luego puedes asignar memoria dinámicamente usando malloc
. Es una práctica común almacenar la dirección en un puntero ...
int* p = 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 new
operador, y la 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 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 a free()
o delete
para evitar perder la memoria, 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...
C ++ punteros inteligentes
En C ++, es una práctica recomendada utilizar objetos de puntero inteligente para almacenar y administrar los punteros, asignándolos de forma automática cuando se ejecutan los destructores de punteros inteligentes. Desde 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 compartir propiedad (utilizando el recuento de referencias ) ...
{
std::shared_ptr<T> p(new T(3.14, "pi"));
number_storage.may_add(p); // Might copy p into its container
} // p''s destructor will only delete the T if number_storage didn''t copy
Punteros nulos
En C, NULL
y 0
, y adicionalmente en C ++ nullptr
, se pueden usar para indicar que un puntero no contiene actualmente la dirección de memoria de una variable, y no debe ser referenciado ni utilizado en la aritmética de punteros. Por ejemplo:
const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
char c;
while ((c = getopt(argc, argv, "f:")) != EOF)
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 tienen un valor predeterminado de 0
, ni bools
en false
, los punteros no siempre se establecen en NULL
. Todos estos se establecen en 0 / falso / NULL cuando son variables static
o (solo en C ++), las variables miembro directas o indirectas de objetos estáticos o sus bases, o se someten a una inicialización cero (por ejemplo, new T();
y new T(x, y, z);
realice una inicialización cero en los miembros de T, incluidos los punteros, mientras que la new T;
no lo hace).
Además, cuando asigna 0
, NULL
y nullptr
a un puntero, los bits en el puntero no necesariamente se restablecen: el puntero no puede contener "0" en el nivel de hardware, o consulte la dirección 0 en su espacio de direcciones virtuales. Se permite que el compilador almacene algo más allí si tiene motivos para hacerlo, pero haga lo que haga, si viene y compara el puntero con 0
, NULL
, nullptr
u otro puntero que se asignó a cualquiera de ellos, 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 información sobre las direcciones de memoria y por qué es probable que no necesite saber
Más estrictamente, los punteros inicializados almacenan un patrón de bits que identifica NULL
o 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 alguna á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 buscar en diferentes lugares dependiendo de la Instrucciones de código de máquina utilizando la dirección.
Por ejemplo, un int*
correctamente inicializado para apuntar a una variable int
podría - después de convertir a un float*
- acceder a un valor en la memoria "GPU" bastante diferente de la variable int
, luego, una vez que se convierte en un puntero de función puede referirse a una memoria distinta sosteniendo los opcodes de la máquina para la función.
Los lenguajes de programación 3GL como C y C ++ tienden a ocultar esta complejidad, de manera que:
Si el compilador le da un puntero a una variable o función, puede desreferenciarlo libremente (siempre y cuando la variable no se haya destruido / desasignado mientras tanto) y es un problema del compilador si, por ejemplo, un registro de CPU en particular debe restaurarse de antemano, o un distintivo instrucción de código de máquina 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 que se encuentra más allá del final de la matriz que es legal para comparar con otros punteros a elementos en la matriz (o que se han movido de manera similar mediante la aritmética de punteros al mismo valor de uno más allá del final); de nuevo en C y C ++, depende del compilador asegurarse de que "simplemente funciona"
Las funciones específicas del sistema operativo, por ejemplo, la asignación de memoria compartida, pueden proporcionarle punteros, y "simplemente funcionarán" dentro del rango de direcciones que tengan sentido para ellos
Los intentos de mover los punteros legales más allá de estos límites, o de convertir números arbitrarios a punteros, o usar punteros convertidos en tipos no relacionados, generalmente tienen un comportamiento indefinido , por lo que deben evitarse en bibliotecas y aplicaciones de nivel superior, pero el código para sistemas operativos, controladores de dispositivos, etc. Es posible que deban confiar en el comportamiento no definido por C o C ++, que sin embargo está bien definido por su hardware específico.
Por favor incluya un ejemplo con la explicación.
Código y explicación de cslibrary.stanford.edu/106 :
La operación de desreferenciación comienza en el puntero y sigue su flecha hacia arriba para acceder a su pointee. El objetivo puede ser mirar el estado de los pointee o cambiar el estado de los pointee. La operación de desreferencia en un puntero solo funciona si el puntero tiene un pointee: el pointee debe asignarse y el puntero debe configurarse para señalarlo. El error más común en el código del puntero es olvidarse de configurar el pointee. 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, el sistema de ejecución marcará cortésmente la falta de referencia incorrecta. En lenguajes compilados como C, C ++ y Pascal, la falta de referencia incorrecta a veces se bloquea y otras veces corrompe la memoria de una manera sutil y aleatoria. Los errores de puntero en los 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
}
Creo que todas las respuestas anteriores son incorrectas, ya que afirman que la anulación de la referencia 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 denomina "desreferenciación" del puntero.
Dicho esto, podemos desreferenciar el puntero sin acceder nunca al valor al que apunta. Por ejemplo:
char *p = NULL;
*p;
Hemos hecho referencia al puntero NULL sin acceder a su valor. O podríamos hacer:
p1 = &(*p);
sz = sizeof(*p);
Nuevamente, desreferenciación, pero nunca accediendo al valor. Dicho código NO se bloqueará: el bloqueo ocurre cuando en realidad accede a los datos mediante un puntero no válido. Sin embargo, desafortunadamente, de acuerdo con el estándar, la anulación de 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 simplemente devuelve un valor l para su uso futuro.
Eliminar la referencia a un puntero significa obtener el valor que se almacena en la ubicación de la 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.
En palabras sencillas, la anulación de referencia significa acceder al valor desde una determinada ubicación de memoria a la que apunta el puntero.
Un puntero es una "referencia" a un valor. Al igual que un número de llamada de biblioteca es una referencia a un libro. "Desreferenciación" el número de 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 una persona que va a encontrar un libro que no está allí.