c++ undefined-behavior

c++ - ¿Por qué no debería intentar usar "este" valor después de "eliminar esto"?



undefined-behavior (7)

En este párrafo de C ++ FAQ , se discute el uso de delete this construcción. Se enumeran 4 restricciones.

Las restricciones 1 a 3 parecen bastante razonables. Pero, ¿por qué hay restricción 4 allí que "no debo examinarlo, compararlo con otro puntero, compararlo con NULL, imprimirlo, echarlo, hacer algo con él"?

Quiero decir, this es otro puntero. ¿Por qué no puedo reinterpret_cast a un int o llamar a printf() para generar su valor?


Aha!

3.7.3.2/4: "... la función de desasignación desasignará el almacenamiento al que hace referencia el puntero, dejando inválidos todos los punteros que hacen referencia a cualquier parte del almacenamiento desasignado. El efecto de usar un valor de puntero no válido (incluido el pasarlo a una desasignación) función) no está definido ".

Tenga en cuenta que esto dice "usar el valor", no "desreferenciar el puntero".

Ese párrafo no es específico para this , se aplica a todo lo que se ha eliminado.


El valor de ''this'' después de llamar a delete no está definido, y el comportamiento de todo lo que haga con él tampoco está definido. Si bien esperaría que la mayoría de los compiladores hicieran algo sensato, no hay nada (en la especificación) que impida que el compilador decida que su comportamiento en este caso particular será emitir código para formatear su disco duro. Invocar un comportamiento indefinido es (casi) siempre un error, incluso cuando su compilador particular se comporta de la manera que le gustaría.

Puede solucionar esto tomando una copia del puntero (como un entero) antes de llamar a eliminar.


En un programa de subprocesos múltiples, en el momento en que delete un puntero, el espacio libre puede ser asignado por otro subproceso, sobrescribiendo el espacio utilizado por this . Incluso en un programa de un solo hilo, a menos que tenga mucho cuidado con lo que llama antes de return , cualquier cosa que haga después de delete this podría asignar memoria y sobrescribir lo que solía ser señalado por this .

En un ejecutable de Microsoft Visual C ++ compilado en modo Depuración, al delete un puntero su memoria se sobrescribe inmediatamente con un patrón de prueba 0xCC (las variables no inicializadas también se inicializan con este patrón) para ayudar a identificar errores de puntero colgantes como este.

Esto me recuerda cuando arreglé un error en un juego jugable en línea en el que el constructor de un objeto Fire borraba el Fire más antiguo si el número total de Incendios había alcanzado un cierto número. El Fuego borrado a veces era el Fuego padre creando un nuevo Fire - bam, ¡error de puntero colgante! Fue solo por suerte que este error interactuó con el algoritmo de asignación de memoria de una manera completamente predecible (el Fire borrado siempre se sobrescribía con un nuevo Fire de la misma manera); de lo contrario, habría provocado una desincronización entre los jugadores en línea. Encontré este error al reescribir la forma en que el juego asignaba la memoria. Debido a su predictibilidad, cuando lo arreglé, también pude implementar la emulación de su comportamiento para compatibilidad con clientes de juegos más antiguos.


La razón por la que no puede hacer nada con un puntero después de eliminarlo (este o cualquier otro puntero) es que el hardware podría (y algunas máquinas más antiguas lo hicieron) interceptar intentando cargar una dirección de memoria no válida en un registro. Aunque puede estar bien en todo el hardware moderno, el estándar dice que lo único que puede hacer con un puntero no válido (no inicializado o eliminado) es asignarlo (ya sea NULL o desde otro puntero válido).


Por la misma razón, no eliminará ningún otro puntero y luego intentará realizar cualquier operación en él.


b / c la dirección a la que se refiere ahora, no está definida, y usted no sabe lo que podría estar allí ...


porque cualquier acción que pueda tomar con ese puntero podría desencadenar lógica que se interpreta en los métodos de clase de ese objeto, lo que podría provocar un bloqueo.

Ahora, algunas de las acciones que señala pueden ser aparentemente "seguras", pero es difícil decir qué sucede con cualquier método al que pueda llamar.

Desde la publicación: "no debe examinarlo, compararlo con otro puntero, compararlo con NULL, imprimirlo, emitirlo, hacer cualquier cosa con él"?

Todas estas acciones pueden desencadenar funciones relacionadas con el operador, que se evalúan con un puntero indefinido. Idem para el casting.

Ahora bien, si realizas un reintepretcast, probablemente sea una historia diferente, y probablemente te lleves bien, ya que la reinterpretación es una reinterpretación poco a poco, sin involucrar (hasta donde yo sé) ninguna llamada al método.