He escrito un sencillo trabajo de juego de tetris con cada bloque como una instancia de una clase singleblock.

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 escaneos para completar la línea, y se ejecuta a través de la lista enlazada de bloques de eliminar las opciones pertinentes y la reasignación de la ->siguiente punteros.

SingleBlock *deleteBlock;
SingleBlock *tempBlock;

tempBlock = deleteBlock->next;
delete deleteBlock;

Funciona el juego, los bloques se eliminan correctamente y todo funciona como debe. Sin embargo, en la inspección aún puedo acceder de bits aleatorios de los datos eliminados.

Si me printf cada uno de los eliminados singleblocks «x» los valores DESPUÉS de su eliminación, algunos de ellos vuelven al azar de basura (de confirmar la eliminación) y algunos de ellos vuelven 222, diciéndome aunque el destructor fue llamado los datos no se eliminan realmente del montón. Muchas pruebas idénticas mostrar siempre es el mismo cuadras específicas que no son eliminados 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

Es ser capaz de acceder a los datos desde más allá de la tumba espera?

Lo siento si es un poco largo de explicar.

  • El método más seguro es la eliminación de un elemento que ya no se usa, y nunca se refieren a ella de nuevo. Punteros inteligentes pueden ayudar cuando más de un puntero que apunta al mismo objeto en memoria.
  • Si puede acceder a los bloques, puede volver a eliminarlos. Eso es malo. Que no lo haga.
  • A veces creo que la mejor palabra clave que delete habría sido forget; en realidad, no se indica al compilador que eliminar nada tanto como dejar de preocuparse acerca de ello (y dejar que alguien más haga lo que quiera con i) tipo de como devolver un libro a la biblioteca en lugar de la quema.
  • La forma en que este código está estructurado, los Poliuretanos de clase no es responsable de la manipulación de sus propios miembros. Mientras que esto es legal C++ (compila, y no depende de un comportamiento indefinido – ignorando el acceso después de eliminar que estás talkinga aquí), es realmente un C-estilo del programa. Intenta hacer Poliuretanos de manejar sus propios miembros, incluyendo las operaciones de eliminación. Si no es demasiado difícil, evite exponer la cruda punteros fuera de la clase. Esta encapsulación generalmente se ahorrará a partir de toda una serie de errores o pérdidas de memoria hacia abajo de la línea.
  • Estoy de acuerdo con Thomas Matthews. El uso de punteros inteligentes si usted puede (boost biblioteca shared_pointer es un muy buen propósito general uno). Si usted no desea llevar a cabo la biblioteca de la dependencia, trate de usar un std::list o std::vector en lugar de manualmente la codificación de una lista enlazada/asigna el montón ampliable implementación de matriz.
  • Este es un caso de buenas prácticas.

InformationsquelleAutor Ash | 2009-12-18

13 Comentarios

  1. 78

    Es ser capaz de acceder a los datos desde más allá de la tumba espera?

    Esto se conoce técnicamente como un Comportamiento Indefinido. No se sorprenda si se le ofrece una cerveza bien.

    • También, es bueno añadir el corolario de que el hecho de que… Si uno tenía datos de que es «sensible» almacenados en la memoria, se debe considerar una buena práctica es completamente sobrescribir antes de eliminarlo (a fin de evitar que otros segmentos de código de acceder a él).
    • Que deben ser tratados antes de que el dtor de la llamada.
    • … o al menos en el dtor.
    • Sí, yo creo que el destructor es el lugar adecuado. No quiero hacerlo demasiado pronto, y que no se puede hacer demasiado tarde.
    • Uno sólo tiene que hacer realmente seguro de que no optimizado, ya que no la conducta observable. (El uso de una API de la función de la garantía de que no se podan, no memset.)
  2. 31

    Es ser capaz de acceder a los datos desde más allá de la tumba espera?

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

    Tenga en cuenta que el comportamiento no está definido. El uso de ciertos compiladores, la memoria puede ser puesto a cero. Cuando usted llame a eliminar, lo que pasa es que la memoria está marcado como disponible, así que la próxima vez que alguien hace nueva, la memoria puede ser utilizado.

    Si usted piensa acerca de ello, es lógico – cuando usted le dice al compilador que ya no está interesado en la memoria (mediante eliminar), ¿por qué debería el equipo pasar un tiempo en la puesta a cero de la misma.

    • Sin embargo, no hay ninguna garantía de que new o malloc no asignar algunos de los nuevos objetos en la parte superior de las antiguas. Otro desastre puede ser el sistema recolector de basura. También, si su programa se concede la memoria de una memoria del sistema de la piscina, otros programas pueden escribir sobre el fantasma de datos.
    • En realidad, no. Con éxito el acceso borrado de la memoria no es el comportamiento esperado, es un comportamiento indefinido. Otra asignación podría sobrescribir la memoria que acaba de liberar.
    • Matthews no estoy diciendo que es una buena idea tratar de acceder a ella. @Curt Nichols Eso es jugar con las palabras. Dependiendo de qué compilador que utilice, puede espera que la memoria no es cero inmediatamente cuando se llama a eliminar. Obviamente, puede no ser seguro acerca de ella, aunque.
  3. 9

    Es lo que C++ llama a un comportamiento indefinido – usted podría ser capaz de acceder a los datos, es posible que no. En cualquier caso, es la cosa incorrecta a hacer.

  4. 9

    Eliminar, no se elimina nada, simplemente las marcas de la memoria como «ser libre para su reutilización». Hasta que alguna de llamadas de asignación de reservas y llena ese espacio que tendrán los datos antiguos. Sin embargo, confiando en que es un gran no-no, básicamente si se elimina algo olvidarse de él.

    Una de las prácticas en este sentido que a menudo se encuentran en las bibliotecas es una función de borrado:

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

    Esto nos impide accidentalmente acceso de memoria no válida.

    Nota que es perfectamente aceptable para llamar delete NULL;.

    • Incluso si usted no usa una macro, es una buena práctica establecer un puntero a NULL inmediatamente después de liberarlo. Es un buen hábito para entrar en, la prevención de este tipo de malentendidos.
    • Cualquier biblioteca de C++ que utiliza una macro sería muy sospechoso, en mi humilde opinión. En el leasy, debe ser una línea de plantilla de función.
    • Configuración de punteros a NULL siguientes eliminar no es universal buenas prácticas en C++. Hay veces en que es una buena cosa que hacer, y a veces es inútil y puede ocultar los errores.
    • OGRO? Y un par más que he visto. Pero sí, una función de plantilla es mucho mejor, y estoy totalmente de acuerdo en esta práctica 🙂
    • Odio a esta práctica. Es muy desordenados, y meh.
    • «Esto nos impide accidentalmente acceso de memoria no válida». Esto no es cierto, y demuestra por qué utilizar este truco se debe esperar a que se correlaciona con la escritura de código incorrecto. char *ptr = new char; char *ptr2 = ptr; Delete(ptr); *ptr2 = 0;. Yo accidentalmente acceso de memoria no válida. Es simplemente confuso pensamiento a null a un referencia, en la creencia de que de esta forma se protege el objeto se refiere. También, no se olvide que usted necesita una versión diferente de esta función para punteros a arrays.
    • cuando ajuste el puntero a NULL después de la eliminación de ocultar un error?
    • cuando se tiene una función que supone que sólo se llama una vez, que elimina el puntero y, a continuación, los valores null de ella. Algunos errónea código en otros lugares se llama a la función dos veces, y nada va mal, a pesar del hecho de que este otro código tiene un doble-libre de error, que fácilmente podría convertirse en un usuario después de que libre de error en el futuro, o en circunstancias ligeramente diferentes.
    • Por supuesto, usted puede comprobar el valor null antes de eliminar (y de ahí coger el doble libre de error), pero entonces se pierde la capacidad para almacenar null en ese campo en la no-casos de error.
    • Usted no puede confiar en ser capaz de leer los datos, incluso sin más asignaciones, como el asignador es libre de utilizar para cualquier propósito, tales como una lista enlazada para el almacenamiento de bloques libres.

  5. 3

    Montón de memoria es como un montón de pizarras. Imagine que usted es un maestro. Mientras que usted está enseñando a su clase, la pizarra le pertenece a usted, y usted puede hacer lo que quiera con ella. Usted puede garabatear en ella y sobrescribir cosas como usted desea.

    Cuando la clase ha terminado y que están a punto de salir de la habitación, no hay una política que requiere que usted para borrar la pizarra — simplemente la mano de la pizarra a la siguiente maestro que generalmente será capaz de ver lo que usted escribió.

    • Si un compilador puede determinar que código es, inevitablemente, va a tener acceso (incluso vistazo a) la parte de la pizarra no es propio, tal determinación libre el compilador a partir de las leyes del tiempo y la causalidad; algunos compiladores de la explotación de un modo que habría sido considerado absurdo de una década atrás (muchos de los cuales todavía son absurdas, en mi humilde opinión). Yo podría entender a decir que si dos piezas de código no dependen unos de otros un compilador puede intercalar su procesamiento en la industria de la moda, incluso si las causas de la UB a golpear «principios», pero una vez que la UB se convierte en inevitable de todas las reglas que volar por la ventana.
  6. 2

    El sistema de la memoria no se borra cuando se liberan a través de delete(). El contenido, por lo tanto, accesible hasta que la memoria se asigna para la reutilización y el sobrescrito.

  7. 1

    eliminar cancela la asignación de la memoria, pero no la modifica o cero. Todavía no debe acceder a desasignación de memoria.

  8. 1

    Después de eliminar un objeto no definido lo que va a pasar a los contenidos de la memoria que ocupaban. Esto no significa que la memoria es libre para ser re-utilizado, pero la aplicación no tiene que sobrescribir los datos que estaba allí originalmente, y no tienen la reutilización de la memoria de inmediato.

    Usted no debería tener acceso a la memoria después de que el objeto se ha ido, pero no debería ser surpising que algunos de los datos permanece intacto allí.

  9. 0

    No cero/cambio de la memoria aún… pero en algún punto, la alfombra va a ser sacó de debajo de sus pies.

    No es, ciertamente, no son predecibles: depende de cómo de rápido memoria de asignación/desasignación se bate.

  10. 0

    Sí, se puede esperar a veces. Mientras que new se reserva espacio para los datos, delete simplemente invalida un puntero creado con new, permitiendo que los datos sean escritos en los que anteriormente estaban reservadas lugares; no necesariamente eliminar los datos. Sin embargo, usted no debe confiar en que el comportamiento debido a los datos en esos lugares podría cambiar en cualquier momento, posiblemente a causa de su programa de portarse mal. Esto es por qué después de utilizar delete en un puntero (o delete[] en una matriz asignados con new[]), debe asignar NULL a fin de que no se puede manipular con un puntero no válido, suponiendo que usted no asignar la memoria usando new o new[] antes de usar ese puntero de nuevo.

    • No hay nada en el lenguaje C++ estándar de la prevención de delete de borrado de la memoria que se ha eliminado o llenado con un valor extraño. Es definido por la implementación.
  11. 0

    Que conducirá a un comportamiento indefinido y eliminar cancela la asignación de memoria , no se reinicializa con cero .

    Si quieres hacer es cero, a continuación, hacer :

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

    Aunque es posible que su tiempo de ejecución no se informe de este error, el uso de una adecuada comprobación de errores en tiempo de ejecución, tales como Valgrind se alerta de que el uso de la memoria después de que ha sido liberado.

    Recomiendo que si usted escribir código con new/delete y raw punteros (en lugar de std::make_shared() y similares), que el ejercicio de su unidad de pruebas bajo Valgrind para al menos tener una oportunidad de detectar dichos errores.

  13. -1

    Bueno, he estado pensando en esto por un buen rato así, y he tratado de ejecutar algunas pruebas para entender mejor lo que está pasando bajo el capó. La respuesta estándar es que después de llamar eliminar usted no debe esperar nada bueno de acceder a la memoria del lugar.
    Sin embargo, esto no parece suficiente para mí. ¿Qué es lo que realmente sucede cuando se llama a eliminar(ptr)? He aquí lo que he encontrado. Estoy usando g++ en Ubuntu 16.04, por lo que este puede jugar un papel en los resultados.

    Lo que me espera primero cuando se utiliza el operador delete fue que la memoria liberada sería entregada de nuevo el sistema para su uso en otros procesos. Permítanme decir esto no sucede bajo cualquiera de las circunstancias que he probado.

    De memoria liberado con eliminar todavía parecen ser asignados para el programa primero se asigna con nueva. Lo he intentado, y no hay ningún uso de la memoria disminuir después de llamar a eliminar. Tuve un software que allcated alrededor de 30 mb de listas a través de nueva llamadas, y luego la soltó con posterior eliminar llamadas. Lo que sucedió es que, mirando el monitor del Sistema, mientras que el programa se estaba ejecutando, incluso de un largo sueño después de la eliminar llamadas, el consumo de memoria a mi el programa era el mismo. Ninguna disminución! Esto significa que eliminar no libera memoria para el sistema.

    De hecho, parece que la memoria asignada por un programa es su para siempre! Sin embargo, el punto es que, si se cancela la asignación, la memoria se puede utilizar de nuevo el mismo programa, sin tener que asignar más. He intentado asignar 15MB, la liberación de ellos, y, a continuación, asignar otro de 15 mb de datos, y después de que el programa nunca se utiliza 30MB. El monitor de sistema siempre mostró alrededor de 15MB. Lo que yo hice, respecto a la prueba anterior, fue solo para cambiar el orden en que sucedieron las cosas: la mitad de la asignación, la mitad de desasignación, otra mitad de la asignación.

    Así, al parecer la memoria utilizada por un programa puede aumentar, pero nunca se reducen. Pensé que tal vez la memoria sería realmente ser liberados para otros procesos en situaciones críticas, tales como cuando no hay más memoria disponible. Después de todo, ¿qué sentido tendría que hacer para que un programa de mantener su propia memoria para siempre, cuando otros procesos se preguntan por ella? Así que me asigna el 30MB de nuevo, y mientras que la cancelación de la asignación ellos puedo ejecutar un memtester con la cantidad de memoria física que podía. Yo esperaba ver mi software de la mano de su memoria a memtester. Pero supongo que no sucedió!

    He hecho un corto video que muestra la cosa en acción:

    C++ eliminar - borra mis objetos, pero aún puedo acceder a los datos?

    A ser 100% honesto, no fue una situación en la que algo sucedido. Cuando traté de memtester con más de la memoria física disponible en el medio de la cancelación de proceso de mi programa, la memoria utilizada por el programa se redujo a alrededor de 3MB. El memtester proceso fue asesinado de forma automática, sin embargo, y lo que sucedió fue aún más sorprendente! El uso de la memoria de mi programa de aumento con cada borrar llamada! Fue como si Ubuntu fue la restauración de toda su memoria de nuevo después de la memtester incidente.

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

Dejar respuesta

Please enter your comment!
Please enter your name here