2010-12-08 15 views
6

Se ha hecho evidente a través de un series of SO questions hoy que tengo una comprensión muy pobre de la verdadera naturaleza de los punteros, referencias y valores.Comprensión de referencias frente a punteros. ¿Por qué funciona esto?

Considere el siguiente código:

int* p = new int(3); 
int& r = *p; 

cout << " p = " << p << "\t*p = " << *p << endl; 
cout << "&r = " << &r << "\t r = " << r << endl; 

delete p; 

cout << "&r = " << &r << "\t r = " << r << endl; 

int v = 4; 
r = v; 

cout << "&r = " << &r << "\t r = " << r << endl; 

La salida de esto es

p = 0x1001000b0 *p = 3 
&r = 0x1001000b0  r = 3 
&r = 0x1001000b0  r = 3 
&r = 0x1001000b0  r = 4 

Lo que no entiendo es por qué la segunda vez que se imprime el valor de la referencia que Don recibes un error El puntero correspondiente al valor de la referencia ya ha sido eliminado. Desde mi previous question, casi me había convencido de que cualquier declaración como r = x hace una copia de x en el lugar del valor que se refiere a r. Sin embargo, si este fuera el caso, entonces las direcciones p y &r serían diferentes, ¿no? Si ya he llamado eliminar en 0x100100b0, ¿cómo puedo seguir usándolo?

Verdadero o falso: una referencia es lo mismo que un alias del valor en una dirección.

Verdadero o falso: Si elimina un puntero a la misma dirección como un valor de referencia reside, (como yo arriba), entonces no hay un comportamiento indefinido ocurrirá, y nadie va a sobrescribir esa dirección, siempre y cuando la referencia existe

+9

Lo que ocurre con el comportamiento indefinido es que está perfectamente permitido que parezca funcionar bien. :) –

+0

Verdadero, falso, y qué dijo @Karl. Este es un comportamiento indefinido que no se bloqueó esta vez. – aschepler

+0

@ ustedes muchachos! Entonces creo que entiendo finalmente. – JnBrymn

Respuesta

11

Aunque no obtenga un error, los resultados aún no están definidos. Comportamiento no definido significa que cualquier cosa puede suceder, incluido el programa que parece seguir funcionando correctamente.

Una referencia es lo mismo que un alias del valor en una dirección.

Esto es efectivamente cierto. Sería más correcto decir que una referencia es un alias para un objeto (no es un valor).

Si elimina un puntero a la misma dirección como un valor de referencia reside, (como yo arriba), entonces no hay un comportamiento indefinido ocurrirá,

Esto también es cierto. No hay un comportamiento definido hasta que intente utilizar la referencia. Cuando intenta utilizar la referencia (por ejemplo, a través de &r), obtiene un comportamiento indefinido.

Si nunca intenta utilizar la referencia después de destruir el objeto, no hay un comportamiento indefinido.

nadie sobrescribirá esa dirección siempre que la referencia exista.

No, esto no es correcto. Tan pronto como se destruye el objeto, cualquier referencia o puntero a él es inválido e inutilizable. Si intenta utilizar un puntero o referencia a un objeto destruido, los resultados no están definidos.

+0

Aclaración. ¡Gracias! – JnBrymn

+0

Otra pregunta, ¿no es 'r = v;' volver a colocar la referencia? – Praetorian

+2

@Praetorian: No, asigna el valor de 'v' (que es 4) al objeto al que hace referencia la referencia' r'. Es imposible volver a colocar una referencia. –

0

El primero es cierto:

  • Una referencia es de hecho un alias a un valor. La implementación específica de la referencia no cambia ese hecho y no se debe confiar en ella.

La segunda es falsa:

  • Si elimina un puntero a la misma dirección, la única fuente definitiva de lo que ocurre si se utiliza la referencia previamente creada es la especificación del lenguaje. Creo que el caso en C++ es "comportamiento indefinido". Lo que sucede en su ejemplo es que (estoy hipotizando aquí), la referencia se implementa como un puntero y, por lo tanto, apunta al bloque de memoria que tiene el valor 3 cuando se interpreta como int. Ese bloque se ha lanzado al montón para su posible uso futuro, por lo que no hay garantía de que lo que contiene sea en realidad 3 o incluso un valor legal para el tipo al que se hace referencia (aunque, por supuesto, todos los valores posibles son legales para int; podría tener ha sido un tipo de clase, sin embargo). En la práctica, dado que el valor no se sobrescribió con ninguna otra cosa, se obtiene un resultado "significativo" cuando se usa la referencia. Sin embargo, esto sucede por pura "coincidencia".

Actualización: que estaba equivocado en respuesta a la primera pregunta, han vuelto a trabajar la respuesta.

0

A reference is the same thing as an alias to the value at an address.

verdadera

True or false: If you delete a pointer to the same address as a referenced value resides, (as I do above), then no undefined behavior will occur, and no one will ever overwrite that address as long as the reference exists.

Falso

int v = 4; 
r = v; 

compila porque r es una referencia a un int.

Sin embargo, cuando elimina lo que apunta a una referencia y luego trata de usarlo, ¡es un comportamiento indefinido!

0

Su primera pregunta es un poco demasiado vaga para responder. ¿Qué es un alias para el valor en una dirección?

El segundo es definitivamente falso. El recuerdo al que apuntaba p se liberó y puede reutilizarse. Simplemente sucede que aún no ha cambiado. No está definido el uso de r y r = v desreferenciados r y copiado el valor de v allí (que tampoco estaba definido, podría haberse bloqueado).

Cuando algo no está definido, eso no significa que obtendrá un error. Significa que cualquier comportamiento que obtenga se considerará según las especificaciones. Chocar o trabajar son comportamientos "indefinidos" aceptables.

0

Verdadero o falso: una referencia es lo mismo que un alias del valor en una dirección.

False: No tiene nada que ver con la dirección (es un detalle de la implementación).

Verdadero o falso: Si elimina un puntero a la misma dirección como un valor de referencia reside, (como yo arriba), entonces no hay un comportamiento indefinido ocurrirá, y nadie va a sobrescribir esa dirección, siempre y cuando la referencia existe

False. Es un comportamiento indefinido utilizar una referencia a una variable que ya no está activa. Diferentes tipos de variables mueren en diferentes momentos.

  • variables de duración de almacenamiento automático mueren al final del alcance.
  • variables de duración de almacenamiento estático mueren en el orden inverso de creación después de las salidas principales
  • variables de duración de almacenamiento dinámico mueren cuando se invoca delete en la memoria en la que residen.
+1

El primero es técnicamente cierto, creo. Una referencia es un alias de un objeto y un objeto es un valor y tiene una dirección (tal vez sería mejor decir "un objeto tiene un valor"? No estoy seguro). El OP usa la palabra "alias", que es correcta. –

+0

Sí, pero es un alias de un objeto. Cómo se representa el objeto es invisible para el código que utiliza la referencia (podría estar en el registro). –

1

Una referencia es un alias de un objeto. Si la vida útil de ese objeto ha finalizado, la referencia a ese objeto deja de ser válida.

En su ejemplo, utilizando referecen r después de los delete p operación da como resultado un comportamiento indefinido - el hecho de que 'parece' al trabajo es sólo una coincidencia - usando r después de ese punto es tan válido como el uso de *p después de ese punto (y vería debilitada tendrá como resultado el mismo comportamiento

Por ejemplo, la modificación de su programa para hacer las mismas thigns con *p como con r tiene el siguiente aspecto:.

#include <iostream> 
using namespace std; 

int main() 
{ 
    int* p = new int(3); 
    int& r = *p; 

    cout << " p = " << p << "\t*p = " << *p << endl; 
    cout << "&r = " << &r << "\t r = " << r << endl; 

    delete p; 

    cout << " p = " << p << "\t*p = " << *p << endl; 
    cout << "&r = " << &r << "\t r = " << r << endl; 

    int v = 4; 

    *p = v+1; 
    cout << " p = " << p << "\t*p = " << *p << endl; 

    r = v; 
    cout << "&r = " << &r << "\t r = " << r << endl; 
} 

Salida:

p = 0x3f1730 *p = 3 
&r = 0x3f1730 r = 3 
p = 0x3f1730 *p = 4134736 
&r = 0x3f1730 r = 4134736 
p = 0x3f1730 *p = 5 
&r = 0x3f1730 r = 4 

Usted verá que hay un comportamiento similar para el uso de *p después de que el objeto ha sido eliminado (y es igual de válido).

En todas las instancias, acceder al objeto una vez que se ha eliminado es un comportamiento indefinido, ya sea que el acceso se produzca a través de un puntero no válido o una referencia no válida.

+0

Al final algo más escribe sobre esa dirección de memoria desde que la liberamos, y entonces el mundo termina. ¿Derecha? – JnBrymn

+0

@John: o podría bloquearse en el momento de leer o escribir el objeto, ya que la dirección de memoria virtual para el objeto podría no ser válida (esto es preferible desde el punto de vista de la depuración, ya que le permitirá saber inmediatamente que hecho algo malo). O puede que no se note nada malo (este es el peor caso, ya que el problema podría no darse a conocer hasta varias semanas y muchas instalaciones más adelante). –

1

Verdadero o falso: Una referencia es la misma cosa como un alias para el valor en una dirección .

Errr ... ¿verdad? Tu terminología es lo que lo hace poco claro, pero creo que obtuve lo que estás preguntando.

Verdadero o falso: Si elimina un puntero a la misma dirección como valor de referencia reside, (como yo arriba), entonces no se producirá un comportamiento indefinido, y nadie nunca sustituir dicho dirección siempre y cuando exista la referencia .

False. Se está recuperando al leer el valor correcto después de la memoria no asignada solo porque su implementación en C++ no es paranoica en cuanto a la administración de la memoria y accede a ella justo después de que expiró su "validez".

Verá, en tiempo de compilación no hay forma de que el compilador prediga que va a sacar este sucio truco y en la memoria de tiempo de ejecución la microgestión de seguridad es costosa.

Entonces, ¿por qué esta es una mala práctica y qué puede salir mal? 1) si entre las "borrar p" y la 3ra cita se realizan grandes operaciones de memoria con muchas llamadas "nuevas" y "eliminar", existe una gran posibilidad de que un punto de memoria real "r" haga referencia a se verá comprometido (su valor cambiado o totalmente no disponible debido a la liberación de memoria al sistema operativo - que causará un error de protección general y se bloqueará la aplicación)

2) si compila y ejecuta su aplicación de alguna forma paranoica (o recurso escaso) entorno obtendrá un bloqueo porque el sistema rastrea qué memoria pertenece a su aplicación incluso a una escala tan baja como el valor "int".

Si desea obtener más información, puede buscar temas sobre "C++ heap" y "C++ memory management".

Cuestiones relacionadas