2010-07-19 15 views
20

Estoy jugando un poco con la asignación dinámica de memoria, pero no entiendo nada. Al asignar algo de memoria con la instrucción new, se supone que puedo destruir la memoria a la que apunta el puntero usando delete.¿Por qué no eliminar destruir algo?

Pero cuando intento, este comando delete no parece funcionar ya que el espacio al que apunta el puntero no parece haberse vaciado.

Tomemos esta pieza realmente básico de código como un ejemplo:

#include <iostream> 

using namespace std; 

int main() 
{ 
    //I create a pointer-to-integer pTest, make it point to some new space, 
    // and fulfill this free space with a number; 
    int* pTest; 
    pTest = new int; 
    *(pTest) = 3; 
    cout << *(pTest) << endl; 

    // things are working well so far. Let's destroy this 
    // dynamically allocated space! 
    delete pTest; 

    //OK, now I guess the data pTest pointed to has been destroyed 
    cout << *(pTest) << endl; // Oh... Well, I was mistaking. 

    return 0; 
} 

Alguna pista?

+13

Sí, un quieston novato, pero no tan trivial. – sharptooth

Respuesta

54

Es hora de aprender lo que es un comportamiento indefinido. :)

En C++, cuando haces algo ilegal/sin sentido/malo/etc. el estándar a menudo dice que "conduce a un comportamiento indefinido". Esto significa que desde ese momento en adelante, el estado de su programa no está garantizado por completo, y cualquier cosa podría suceder.

En el punto en el que realiza su último *(pTest), obtiene un comportamiento indefinido. Esto se debe a que pTest no apunta a un objeto válido, y la desreferenciación de dicho puntero no está definida. Entonces, lo que estás viendo está totalmente permitido: producción no definida.

Todo lo que ha hecho es decir "He terminado con esta asignación". Una vez que haya dicho eso, no debería (y de hecho, no puede) inspeccionar ni preocuparse por ese recuerdo por más tiempo. Ni siquiera tiene sentido conceptual desasignar algo y luego tratar de usarlo; ¡Has dicho que terminaste!

Su salida es algo predecible: es probable que su sistema operativo simplemente diga "está bien, gracias por la memoria" y eso es todo. No tiene ninguna razón para "restablecer" la memoria, o hacer algo especial. Eso sería una pérdida de tiempo, cuando nadie (incluido su propio programa) no lo está usando.

Pero recuerde, esta salida es completamente indefinida. No intente usar objetos que no existen. Quizás una mejor prueba habría sido:

#include <iostream> 

struct foo 
{ 
    ~foo() 
    { 
     std::cout << "foo is gone :(" << std::endl; 
    } 
}; 

int main(void) 
{ 
    foo* f = new foo(); 
    delete f; // you'll see that the object is destroyed. 
} 

Aunque parece que estaba buscando ver qué pasa con la memoria en sí misma. Solo recuerde que no tiene sentido deshacerse de la memoria y luego tratar de usarla, por lo que la respuesta es: quién sabe. Depende de su plataforma específica, a C++ no le importa.

+27

Lo malo del comportamiento indefinido es que la mayoría de las veces se ve bien. – peterchen

4

delete operator llama al destructor del objeto y desasigna la memoria previamente asignada al objeto. No afecta a la variable del puntero que apunta al objeto eliminado.

Por lo tanto, al desreferenciar un puntero que apunta a un objeto destruido, obtendrá problemas.

10

Llamar eliminar marcará el área de la memoria como libre. No será necesario restablecer su valor anterior.

Se recomienda establecer el puntero a 0, después de llamar a borrar:

delete pTest; 
pTest = 0; 
+2

En cambio, aconsejaría no usar la variable _at all_ después de llamar a 'delete' (... a menos que, por supuesto, se vuelva a asignar a otra cosa). – stakx

+9

Una solución mejor es asegurarse de que la variable se salga del alcance poco después de la eliminación y, por lo tanto, no se pueda volver a utilizar. –

0

Podría haberse referido a cualquier parte de la memoria mapeada. O tal vez memoria no asignada, dependiendo de cuánto tiempo haya estado en ejecución su programa, detalles de las asignaciones de memoria y si las bibliotecas devuelven memoria al sistema operativo más adelante ...

Si delete borró realmente toda la memoria que se está eliminando, los programas tardarían mucho más tiempo en ejecutarse, ya que perderían mucho tiempo restregando la memoria que probablemente se sobrescribirá tarde o temprano de todos modos. Podría ser bueno para la depuración, pero en el uso de producción, simplemente no hay mucha necesidad de depurar los contenidos de la memoria. (Claves de cifrado son una buena excepción, por supuesto; borrarlos antes de llamar a delete o free es una buena idea.)

1

¿Qué significaría destruir los datos? Supongo que podría ponerlo a cero, pero ¿para qué molestarse? Se supone sucio cuando lo sacamos del medio ambiente, entonces, ¿por qué limpiarlo antes de devolverlo? No nos importa lo que contenga, porque estamos renunciando a nuestro derecho a leerlo. Y en cuanto a porqué de borrado no está cero el indicador sí mismo:

http://www2.research.att.com/~bs/bs_faq2.html#delete-zero

2

Desreferenciar un puntero que apunta a la memoria desasignado es un comportamiento indefinido.

Muchas veces solo funcionará, porque la memoria proporcionada por new es generalmente parte de una porción más grande de memoria asignada que administra el asignador. Cuando llame al delete, llamará a los destructores pertinentes y marcará la memoria como libre, lo que generalmente significa "listo para volver a usar". Por lo tanto, al buscar en esa memoria, encontrará los mismos datos que existían antes de la llamada al delete, o algún otro dato si ese trozo de memoria se ha reasignado después de una llamada new.

Nota que nada prohíbe que el asignador new/delete funciona como una envoltura fina alrededor de las funciones de la memoria virtual del sistema operativo, por lo que cuando todos los bloques asignados con relación a una página ha sido desasignado, se libera toda la página y cualquier intento de acceso resulta en una violación de dirección.

TL, versión DR: no deferencia punteros que apuntan a la memoria desasignada: puede funcionar a veces, a veces le devolverá basura, a veces se activará una infracción de acceso.

Una buena forma de observar de inmediato si está cometiendo este tipo de error es poner sus punteros en NULL después de eliminar la memoria que señalan: si su código intenta desreferenciar un puntero NULL, en casi cualquier sistema esto hacer que la aplicación se bloquee, por lo que las fallas como estas no pasarán desapercibidas.

1

Solo un simple ejemplo para ilustrar lo que podría pasar, y el comportamiento indefinido de que algunas personas mencionaron.

Si añadimos dos líneas adicionales de código antes de la impresión:

delete pTest; 

int *foo = new int; 
*foo = 42; 

cout << *pTest << endl; 

El valor impreso de PTEST podría muy bien ser 3, como lo fue en su caso. Sin embargo, el valor impreso también podría ser 42. Cuando se eliminó el puntero pTest, , se liberó su memoria. Debido a esto, es posible que el puntero foo indique en la misma ubicación en la memoria que pTest solía señalar antes de que fuera borrado.

5

La respuesta es rendimiento.

Es una gran solución de depuración para llenar toda la memoria liberada con un valor no válido (0xCCCCCCCC, 0xDEADDEAD, etc.) para detectar intentos de utilizar punteros obsoletos en la memoria ya liberada.

Pero modificar una memoria liberada cuesta tiempo de CPU, por lo que por motivos de rendimiento, el sistema operativo simplemente agregará el bloque de memoria liberado a su lista "libre" y dejará el contenido intacto.

Cuestiones relacionadas