studio programacion móviles libro ejemplos destructores desarrollo curso aplicaciones c++ pointers undefined-behavior c++-faq

programacion - C++ delete-¿Elimina mis objetos pero todavía puedo acceder a los datos?



manual de programacion android pdf (13)

He escrito un juego de tetris simple y funcional con cada bloque como una instancia de bloque individual de clase.

class SingleBlock { public: SingleBlock(int, int); ~SingleBlock(); int x; int y; SingleBlock *next; }; class MultiBlock { public: MultiBlock(int, int); SingleBlock *c, *d, *e, *f; }; SingleBlock::SingleBlock(int a, int b) { x = a; y = b; } SingleBlock::~SingleBlock() { x = 222; } MultiBlock::MultiBlock(int a, int b) { c = new SingleBlock (a,b); d = c->next = new SingleBlock (a+10,b); e = d->next = new SingleBlock (a+20,b); f = e->next = new SingleBlock (a+30,b); }

Tengo una función que escanea una línea completa y recorre la lista de bloques vinculados, borrando los relevantes y reasignando los siguientes punteros.

SingleBlock *deleteBlock; SingleBlock *tempBlock; tempBlock = deleteBlock->next; delete deleteBlock;

El juego funciona, los bloques se eliminan correctamente y todo funciona como se supone que debe hacerlo. Sin embargo, en la inspección, todavía puedo acceder a los bits aleatorios de los datos eliminados.

Si imprimo cada uno de los valores borrados de los bloques individuales "x" DESPUÉS de su eliminación, algunos de ellos devuelven basura aleatoria (confirmando la eliminación) y algunos devuelven 222, diciéndome que aunque se llamó al destructor los datos no se borraron realmente de el montón Muchos ensayos idénticos muestran que siempre son los mismos bloques específicos que no se eliminan correctamente.

Los resultados:

Existing Blocks: Block: 00E927A8 Block: 00E94290 Block: 00E942B0 Block: 00E942D0 Block: 00E942F0 Block: 00E94500 Block: 00E94520 Block: 00E94540 Block: 00E94560 Block: 00E945B0 Block: 00E945D0 Block: 00E945F0 Block: 00E94610 Block: 00E94660 Block: 00E94680 Block: 00E946A0 Deleting Blocks: Deleting ... 00E942B0, X = 15288000 Deleting ... 00E942D0, X = 15286960 Deleting ... 00E94520, X = 15286992 Deleting ... 00E94540, X = 15270296 Deleting ... 00E94560, X = 222 Deleting ... 00E945D0, X = 15270296 Deleting ... 00E945F0, X = 222 Deleting ... 00E94610, X = 222 Deleting ... 00E94660, X = 15270296 Deleting ... 00E94680, X = 222

¿Se espera tener acceso a datos más allá de la tumba?

Lo siento si esto es un poco largo.


¿Se espera tener acceso a datos más allá de la tumba?

En la mayoría de los casos, sí. Llamar a eliminar no pone a cero la memoria.

Tenga en cuenta que el comportamiento no está definido. Usando ciertos compiladores, la memoria puede ponerse a cero. Cuando llama a eliminar, lo que ocurre es que la memoria está marcada como disponible, por lo que la próxima vez que alguien haga algo nuevo , la memoria puede ser utilizada.

Si lo piensas bien, es lógico: cuando le dices al compilador que ya no estás interesado en la memoria (usando eliminar ), ¿por qué la computadora debería perder tiempo en ponerla a cero?


¿Se espera tener acceso a datos más allá de la tumba?

Esto es técnicamente conocido como Comportamiento Indefinido. No se sorprenda si le ofrece una lata de cerveza tampoco.


Aunque es posible que su tiempo de ejecución no reporte este error, el uso de un tiempo de ejecución correcto de comprobación de errores como Valgrind lo alertará sobre el uso de la memoria después de que se haya liberado.

Recomiendo que si escribe código con new / delete y punteros sin procesar (en lugar de std::make_shared() y similares), ejercite las pruebas de su unidad en Valgrind para al menos tener la posibilidad de detectar dichos errores.


Bueno, me he estado preguntando sobre esto durante bastante tiempo también, y he intentado hacer algunas pruebas para comprender mejor lo que sucede bajo el capó. La respuesta estándar es que después de llamar a eliminar no debe esperar que nada bueno acceda a ese punto de memoria. Sin embargo, esto no me pareció suficiente. ¿Qué está sucediendo realmente al llamar a delete (ptr) ? Esto es lo que he encontrado. Estoy usando g ++ en Ubuntu 16.04, así que esto puede desempeñar un papel en los resultados.

Lo que primero esperaba al usar el operador de eliminación era que la memoria liberada se devolvía al sistema para su uso en otros procesos. Permítanme decir que esto no sucede bajo ninguna de las circunstancias que he intentado.

La memoria liberada con delete aún parece estar asignada al programa que primero la asignó con nueva . Lo he intentado, y no hay una disminución en el uso de memoria después de llamar a eliminar . Tenía un software que asignaba alrededor de 30MB de listas a través de nuevas llamadas, y luego las liberaba con subsiguientes llamadas de eliminación . Lo que sucedió es que, mirando el monitor del sistema mientras el programa se estaba ejecutando, incluso un largo descanso después de las llamadas de eliminación , el consumo de memoria del programa era el mismo. ¡Sin disminución! Esto significa que eliminar no libera memoria en el sistema.

De hecho, parece que la memoria asignada por un programa es suya para siempre. Sin embargo, el punto es que, si se desasigna, la memoria puede ser utilizada nuevamente por el mismo programa sin tener que asignar más. Traté de asignar 15MB, liberarlos, y luego asignar otros 15MB de datos después, y el programa nunca usó 30MB. El monitor del sistema siempre mostró alrededor de 15MB. Lo que hice, con respecto a la prueba anterior, fue simplemente cambiar el orden en que sucedieron las cosas: la mitad de la asignación, la mitad de la asignación, la otra mitad de la asignación.

Por lo tanto, aparentemente la memoria utilizada por un programa puede aumentar, pero nunca reducirse . Pensé que tal vez la memoria realmente se liberaría para otros procesos en situaciones críticas, como cuando no hay más memoria disponible. Después de todo, ¿qué sentido tendría dejar que un programa guarde su propia memoria para siempre, cuando otros procesos lo piden? Así que asigné los 30 MB de nuevo, y mientras los desasigné, memtester un memtester con tanta memoria física como pude. Esperaba ver mi software repartir su memoria a memtester. Pero adivinen, ¡no sucedió!

Inventé un corto de pantalla que muestra lo que está en acción:

Para ser 100% honesto, hubo una situación en la que sucedió algo . Cuando probé memtester con más de la memoria física disponible en el medio del proceso de desasignación de mi programa, la memoria utilizada por mi programa cayó a alrededor de 3MB. Sin embargo, el proceso de memtester se eliminó automáticamente, ¡y lo que sucedió fue aún más sorprendente! ¡El uso de memoria de mi programa aumentó con cada llamada de eliminación! Era como si Ubuntu estuviera restaurando toda su memoria después del incidente del memtester.

Tomado de http://www.thecrowned.org/c-delete-operator-really-frees-memory


Después de eliminar un objeto, no está definido qué sucederá con los contenidos de la memoria que ocupó. Significa que la memoria puede reutilizarse libremente, pero la implementación no tiene que sobrescribir los datos que estaban allí originalmente y no tiene que reutilizar la memoria inmediatamente.

No debe acceder a la memoria después de que el objeto se haya ido, pero no debería sorprender que algunos datos permanezcan intactos allí.


El sistema no borra la memoria cuando la libera mediante delete() . Por lo tanto, todavía se puede acceder al contenido hasta que la memoria se haya asignado para su reutilización y se haya sobrescrito.


Eliminar no elimina nada, simplemente marca la memoria como "libre para reutilizar". Hasta que otras asignaciones de llamadas reserven y llenen ese espacio, tendrán los datos anteriores. Sin embargo, confiar en eso es un gran no-no, básicamente si borras algo, olvídalo.

Una de las prácticas a este respecto que a menudo se encuentra en las bibliotecas es una función Eliminar:

template< class T > void Delete( T*& pointer ) { delete pointer; pointer = NULL; }

Esto nos impide acceder accidentalmente a la memoria no válida.

Tenga en cuenta que está perfectamente bien llamar a delete NULL; .


Es lo que C ++ llama comportamiento no definido: es posible que pueda acceder a los datos, es posible que no. En cualquier caso, es lo incorrecto de hacer.


La memoria del montón es como un montón de pizarras. Imagina que eres un maestro. Mientras enseñas a tu clase, la pizarra te pertenece y puedes hacer lo que quieras con ella. Puedes garabatear y sobrescribir cosas como desees.

Cuando termine la clase y esté a punto de abandonar la sala, no existe una política que requiera que borre la pizarra; simplemente le entrega la pizarra al siguiente profesor que generalmente podrá ver lo que anotó.


Llevará a un comportamiento indefinido y eliminará la memoria de desasignación, no la reiniciará con cero.

Si quieres que sea cero, hazlo:

SingleBlock::~SingleBlock() { x = y = 0 ; }


No se pondrá a cero / cambiará la memoria todavía ... pero en algún momento, la alfombra se va a sacar de debajo de los pies.

No, ciertamente no es predecible: depende de qué tan rápido se produzca la asignación / desasignación de memoria.


Sí, se puede esperar a veces. Mientras que las new reservas de espacio para los datos, delete simplemente invalida un puntero creado con new , permitiendo que los datos se escriban en las ubicaciones previamente reservadas; no necesariamente borra los datos. Sin embargo, no debe confiar en ese comportamiento porque los datos en esas ubicaciones podrían cambiar en cualquier momento, posiblemente causando que su programa se comporte mal. Esta es la razón por la que después de usar delete en un puntero (o delete[] en una matriz asignada con new[] ), debe asignarle NULL para que no pueda manipular un puntero no válido, asumiendo que no asignará memoria usando new o new[] antes de usar ese puntero nuevamente.


delete desasigna la memoria, pero no la modifica ni la pone a cero. Aún así, no debería acceder a la memoria desasignada.