2012-02-07 17 views
17
Obj *op = new Obj; 
Obj *op2 = op; 
delete op; 
delete op2; // What happens here? 

¿Qué es lo peor que puede pasar cuando accidentalmente se elimina por duplicado? ¿Importa? ¿El compilador lanzará un error?¿Qué sucede en una eliminación doble?

+5

Sí, puede importar. No, el compilador no va a "lanzar un error". Simplemente no lo hagas :) – paulsm4

+13

Creo que en la práctica, lo peor que puede * plausiblemente * suceder es que algún tiempo después, el asignador de memoria se comporte mal.Esto (a) lo obliga a pasar mucho tiempo intentando depurarlo, ya que los síntomas no están cerca de la causa y, además, (b) crea una vulnerabilidad de seguridad grave en el software, que su empleador recibe una demanda, pierde, fracasa , y su contrato termina siendo propiedad de un tipo llamado Clive que hace las autorizaciones de la casa a un precio fijo por camión. Te obliga a trabajar tu período de aviso arreglando errores RPG porno tentacular japonés. –

+0

No lo haga y nunca lo haga. Es un comportamiento indefinido. La implementación de C++ puede hacer cualquier cosa en tales casos. Realmente podría hacer cosas malas como corromper su montón, hacer cambios arbitrarios y extraños a otros objetos en el montón y podría incluso peores cosas. Si utiliza punteros sin formato, es mejor configurarlo como un puntero nulo después de la primera eliminación, ya que eliminar el puntero nulo es seguro; de lo contrario, utilice los punteros inteligentes en C++ moderno. – Destructor

Respuesta

19

Causa un comportamiento indefinido. Cualquier cosa puede suceder. En la práctica, es probable que un accidente de tiempo de ejecución sea lo que yo esperaría.

+16

Cachorro aleatorio también podría morir. – Petr

+3

Hubo un caso conocido de un monitor de personas que enciende fuego debido a un comportamiento indefinido. –

16

Es un comportamiento indefinido, por lo que el resultado real variará según el entorno de tiempo de ejecución del compilador &.

En la mayoría de los casos, el compilador no se dará cuenta. En muchos casos, si no en la mayoría, la biblioteca de administración de memoria de tiempo de ejecución se bloqueará.

Debajo del capó, cualquier administrador de memoria tiene que mantener algunos metadatos sobre cada bloque de datos que asigna, de una manera que le permita buscar los metadatos del puntero que devuelve malloc/new. Por lo general, esto toma la forma de una estructura en desplazamiento fijo antes del bloque asignado. Esta estructura puede contener un "número mágico", una constante que es poco probable que ocurra por pura casualidad. Si el administrador de memoria ve el número mágico en el lugar esperado, sabe que es muy probable que el puntero proporcionado para liberar/eliminar sea válido. Si no ve el número mágico, o si ve un número diferente que significa "este puntero fue liberado recientemente", puede ignorar silenciosamente la solicitud gratuita, o puede imprimir un mensaje útil y abortar. Cualquiera de las dos cosas es legal bajo la especificación, y hay argumentos pro/con para cualquiera de los enfoques.

Si el administrador de memoria no mantiene un número mágico en el bloque de metadatos, o no verifica la cordura de los metadatos, entonces puede pasar cualquier cosa. Dependiendo de cómo se implemente el administrador de memoria, el resultado probablemente sea un bloqueo sin un mensaje útil, ya sea inmediatamente en la lógica del administrador de memoria, algo más tarde la próxima vez que el administrador de memoria intente asignar o liberar memoria, o mucho más tarde y más lejos cuando dos partes diferentes del programa piensan que poseen el mismo pedazo de memoria.

Probémoslo. Convierte tu código en un programa completo en so.cpp:

class Obj 
{ 
public: 
    int x; 
}; 

int main(int argc, char* argv[]) 
{ 
    Obj *op = new Obj; 
    Obj *op2 = op; 
    delete op; 
    delete op2; 

    return 0; 
} 

compilarlo (estoy usando gcc 4.2.1 en OSX 10.6.8, pero tu caso es distinto):

[email protected] ~: g++ so.cpp 

Run que:

[email protected] ~: ./a.out 
a.out(1965) malloc: *** error for object 0x100100080: pointer being freed was not allocated 
*** set a breakpoint in malloc_error_break to debug 
Abort trap 

Lookie allí, el tiempo de ejecución gcc realmente detecta que se trataba de un doble borrar y es bastante útil antes de que se estrelle.

+0

También en caso de que exista la posibilidad de que se produzca una "eliminación doble", un puntero inteligente suele ser la mejor solución. – paul23

+0

Buena respuesta, pero estaría más feliz si hicieras una mayor oferta del Comportamiento Indefinido. –

+0

Estoy bastante seguro de que otras respuestas tienen un "comportamiento indefinido" bien cubierto. –

21

Comportamiento indefinido. No hay garantías en absoluto hechas por el estándar. Probablemente su sistema operativo haga algunas garantías, como "no dañará otro proceso", pero eso no ayuda mucho a su programa.

Su programa puede bloquearse. Sus datos podrían estar dañados. El depósito directo de su próximo cheque de pago podría en su lugar sacar 5 millones de dólares de su cuenta.

+24

O podría pedir pizza con su tarjeta de crédito. –

+0

¿Puede reparar mi automóvil? – Goldname

3

El compilador puede dar una advertencia o algo así, especialmente en obvio (como en el ejemplo) pero no es posible detectarlo siempre. (Puede usar algo como valgrind, que en tiempo de ejecución puede detectarlo). En cuanto al comportamiento, puede ser cualquier cosa. Alguna biblioteca segura podría verificar y manejarla bien, pero otros tiempos de ejecución (por velocidad) harán que la suposición de que usted llame sea correcta (lo que no es) y luego se bloquee o empeore.El tiempo de ejecución permite suponer que no está eliminando dos veces (incluso si la eliminación doble haría algo malo, por ejemplo, bloqueando su computadora)

0

No, no es seguro eliminar el mismo puntero dos veces. Es un comportamiento indefinido según el estándar C++.

Desde el C++ FAQ: visitar this link

¿Es seguro para eliminar el mismo puntero dos veces?
¡No! (. Suponiendo que no tuvo que volver de nuevo puntero en el medio)

Por ejemplo, el siguiente es un desastre:

class Foo { /*...*/ }; 
void yourCode() 
{ 
    Foo* p = new Foo(); 
    delete p; 
    delete p; // DISASTER! 
    // ... 
} 

Esa segunda línea de borrar p podrían hacer algunas cosas muy malas para usted. Podría, dependiendo de la fase de la luna, corromper su montón, bloquear su programa, hacer cambios arbitrarios y extraños en objetos que ya están en el montón, etc. Desafortunadamente, estos síntomas pueden aparecer y desaparecer aleatoriamente. Según la ley de Murphy, será golpeado con todas sus fuerzas en el peor momento posible (cuando el cliente está buscando, cuando una transacción de alto valor está tratando de publicar, etc.). Nota: algunos sistemas de tiempo de ejecución lo protegerán de ciertos casos muy simples de eliminación doble. Dependiendo de los detalles, puede estar bien si se está ejecutando en uno de esos sistemas y si nadie implementa su código en otro sistema que maneje las cosas de manera diferente y si está eliminando algo que no tiene un destructor y si no hace nada significativo entre las dos eliminaciones y si nadie cambia su código para hacer algo significativo entre las dos eliminaciones y si su planificador de subprocesos (sobre el cual probablemente no tenga control) no pasa a intercambiar hilos entre los dos eliminan y si, y si, y si. Así que volvamos a Murphy: dado que puede salir mal, lo hará, y saldrá mal en el peor momento posible. Un no-accidente no prueba la ausencia de un error; simplemente no demuestra la presencia de un error. Confía en mí: eliminar dos veces es malo, malo, malo. Solo di no.

0

Todo el mundo ya te dijo que no deberías hacer esto y que esto provocará un comportamiento indefinido. Eso es ampliamente conocido, así que profundicemos en esto en un nivel inferior y veamos qué sucede en realidad.

La respuesta universal estándar es que cualquier cosa puede suceder, eso no es del todo cierto. Por ejemplo, la computadora no intentará matarte por hacer esto (a menos que estés programando AI para un robot) :)

La razón por la cual no puede haber una respuesta universal es que como esto no está definido, es posible que difieren del compilador al compilador e incluso a través de diferentes versiones del mismo compilador.

Pero esto es lo que "más o menos" ocurre en la mayoría de los casos:

delete constan de 2 operaciones primarias:

  • que llama al destructor si se define
  • que de alguna manera se libera la memoria asignada a el objeto

Entonces, si su destructor contiene algún código que acceda a cualquier dato de clase que ya fue eliminado, puede segfau O BIEN (más probable) leerá algunos datos sin sentido. Si estos datos eliminados son punteros, lo más probable es que falle por segmentación, porque intentará acceder a la memoria que contiene algo más, o no le pertenece a usted.

Si su constructor no toca ningún dato o no está presente (no consideremos los destructores virtuales aquí por simplicidad), puede que no sea un motivo de falla en la mayoría de las implementaciones del compilador. Sin embargo, llamar a un destructor no es la única operación que va a suceder aquí.

La memoria debe ser libre. Cómo se hace depende de la implementación en el compilador, pero también puede ejecutar alguna función similar a free, dándole el puntero y el tamaño de su objeto. Llamar al free en la memoria que ya fue eliminada puede bloquearse, porque la memoria puede no pertenecerle más. Si le pertenece, es posible que no se bloquee inmediatamente, pero puede sobrescribir la memoria que ya fue asignada para algún objeto diferente de su programa.

Eso significa que una o más de sus estructuras de memoria acaban de dañarse y su programa probablemente se bloqueará tarde o temprano o podría comportarse de forma increíblemente extraña. Las razones no serán obvias en su depurador y puede pasar semanas averiguando qué diablos acaba de suceder.

Por lo tanto, como han dicho otros, generalmente es una mala idea, pero supongo que ya lo saben. Pero no te preocupes, es muy probable que un gatito inocente no muera si eliminas un objeto dos veces.

Aquí es código de ejemplo que está mal, pero puede funcionar bien también (funciona bien con GCC en Linux):

class a {}; 

int main() 
{ 
    a *test = new a(); 
    delete test; 
    a *test2 = new a(); 
    delete test; 
    return 0; 
} 

Si no crean instancia intermedia de esa clase entre eliminaciones, 2 llama a liberar la memoria en la misma ocurre como se esperaba:

*** Error in `./a.out': double free or corruption (fasttop): 0x000000000111a010 *** 

para responder a sus preguntas directamente:

Qué es lo peor que le puede pasar:

En teoría, su programa causa algo fatal. Incluso puede intentar aleatoriamente borrar tu disco duro en algunos casos extremos. Las posibilidades dependen de cuál es realmente tu programa (¿controlador del núcleo? ¿Programa de espacio de usuario?).

En la práctica, lo más probable es que se bloquee con segfault. Pero algo peor podría suceder.

¿El compilador arrojará un error?

No debería.

0

Si bien esto no está definido:

int* a = new int; 
delete a; 
delete a; // same as your code 

esto está bien definida:

int* a = new int; 
delete a; 
a = nullptr; // or just NULL or 0 if your compiler doesn't support c++11 
delete a; // nothing happens! 

pensé que debía publicarlo ya que nadie más estaba mencionando él.

Cuestiones relacionadas