Comenzaré diciendo, use punteros inteligentes y nunca tendrá que preocuparse por esto.
¿Cuáles son los problemas con el siguiente código?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
Esto fue provocado por una respuesta y comentarios a otra pregunta. Un comentario de Neil Butterworth generó algunos votos a favor:
Establecer punteros en NULL después de eliminar no es una buena práctica universal en C ++. Hay momentos en que es bueno hacerlo, y momentos en los que no tiene sentido y puede ocultar errores.
Hay muchas circunstancias en las que no ayudaría. Pero en mi experiencia, no puede doler. Alguien me ilumine.
c++
pointers
null
dynamic-allocation
Mark Ransom
fuente
fuente
delete
embargo, es seguro para un puntero nulo, que es una razón por la cual poner a cero un puntero puede ser bueno.Respuestas:
Establecer un puntero a 0 (que es "nulo" en C ++ estándar, la definición NULL de C es algo diferente) evita bloqueos en borrados dobles.
Considera lo siguiente:
Mientras:
En otras palabras, si no establece punteros eliminados a 0, se meterá en problemas si está haciendo doble eliminación. Un argumento en contra de establecer punteros en 0 después de la eliminación sería que al hacerlo solo enmascara los errores de doble eliminación y los deja sin controlar.
Obviamente, es mejor no tener errores de doble eliminación, pero dependiendo de la semántica de propiedad y los ciclos de vida de los objetos, esto puede ser difícil de lograr en la práctica. Prefiero un error de doble borrado enmascarado sobre UB.
Finalmente, una nota al margen sobre la administración de la asignación de objetos, le sugiero que eche un vistazo a la
std::unique_ptr
propiedad estricta / singular,std::shared_ptr
a la propiedad compartida u otra implementación de puntero inteligente, según sus necesidades.fuente
NULL
puede enmascarar un error de eliminación doble. (Algunos podrían considerar que esta máscara es realmente una solución; lo es, pero no es muy buena, ya que no llega a la raíz del problema). Pero no establecerla en máscaras NULL en el extremo (¡LEJOS!) Más Problemas comunes de acceso a los datos después de que se han eliminado.unique_ptr
, que hace lo queauto_ptr
intentó hacer, con semántica de movimiento.Establecer punteros en NULL después de haber eliminado lo que apuntaba ciertamente no puede dañar, pero a menudo es una especie de curita sobre un problema más fundamental: ¿Por qué estás usando un puntero en primer lugar? Puedo ver dos razones típicas:
std::vector
funciona, y resuelve el problema de dejar accidentalmente punteros para desasignar la memoria. No hay punteros.new
no sea el mismo que eldelete
invocado. Varios objetos pueden haber usado el objeto simultáneamente mientras tanto. En ese caso, habría sido preferible un puntero compartido o algo similar.Mi regla general es que si dejas punteros en el código de usuario, lo estás haciendo mal. El puntero no debería estar allí para señalar basura en primer lugar. ¿Por qué no hay un objeto que se responsabilice de garantizar su validez? ¿Por qué su alcance no termina cuando lo hace el objeto señalado?
fuente
new
.Tengo una mejor práctica aún mejor: ¡Donde sea posible, finalice el alcance de la variable!
fuente
Siempre establezco un puntero a
NULL
(ahoranullptr
) después de eliminar los objetos a los que apunta.Puede ayudar a atrapar muchas referencias a la memoria liberada (suponiendo que la plataforma falla en un puntero nulo).
No captará todas las referencias a la memoria libre si, por ejemplo, tiene copias del puntero por ahí. Pero algo es mejor que nada.
Enmascarará una eliminación doble, pero creo que son mucho menos comunes que los accesos a la memoria ya liberada.
En muchos casos, el compilador lo va a optimizar. Entonces el argumento de que es innecesario no me convence.
Si ya está utilizando RAII, entonces no hay muchos
delete
s en su código para comenzar, por lo que el argumento de que la asignación adicional causa desorden no me convence.A menudo es conveniente, al depurar, ver el valor nulo en lugar de un puntero obsoleto.
Si esto todavía te molesta, utiliza un puntero inteligente o una referencia en su lugar.
También establecí otros tipos de identificadores de recursos en el valor sin recurso cuando el recurso se libera (que normalmente solo está en el destructor de un contenedor RAII escrito para encapsular el recurso).
Trabajé en un producto comercial grande (9 millones de declaraciones) (principalmente en C). En un momento, usamos macro magia para anular el puntero cuando se liberó la memoria. Esto inmediatamente expuso muchos errores al acecho que se solucionaron rápidamente. Hasta donde puedo recordar, nunca tuvimos un error doblemente libre.
Actualización: Microsoft cree que es una buena práctica para la seguridad y recomienda la práctica en sus políticas SDL. Aparentemente, MSVC ++ 11 pisoteará el puntero eliminado automáticamente (en muchas circunstancias) si compila con la opción / SDL.
fuente
En primer lugar, hay muchas preguntas existentes sobre este tema y temas relacionados, por ejemplo, ¿Por qué no elimina establecer el puntero en NULL? .
En su código, el problema de lo que sucede en (use p). Por ejemplo, si en algún lugar tiene un código como este:
luego establecer p en NULL logra muy poco, ya que todavía tiene que preocuparse por el puntero p2.
Esto no quiere decir que establecer un puntero en NULL siempre sea inútil. Por ejemplo, si p fuera una variable miembro que apunta a un recurso cuya vida útil no era exactamente la misma que la clase que contiene p, entonces establecer p en NULL podría ser una forma útil de indicar la presencia o ausencia del recurso.
fuente
Si hay más código después de
delete
, Sí. Cuando el puntero se elimina en un constructor o al final del método o función, No.El objetivo de esta parábola es recordarle al programador, durante el tiempo de ejecución, que el objeto ya ha sido eliminado.
Una práctica aún mejor es usar punteros inteligentes (compartidos o con alcance) que eliminan automáticamente sus objetos de destino.
fuente
Como han dicho otros,
delete ptr; ptr = 0;
no va a hacer que los demonios salgan volando de tu nariz. Sin embargo, fomenta el uso deptr
una especie de bandera. El código se llena de basuradelete
y establece el puntero enNULL
. El siguiente paso es dispersarse aif (arg == NULL) return;
través de su código para protegerse contra el uso accidental de unNULL
puntero. El problema se produce una vez que las comprobaciones seNULL
convierten en su medio principal para verificar el estado de un objeto o programa.Estoy seguro de que hay un olor a código sobre el uso de un puntero como bandera en algún lugar, pero no he encontrado uno.
fuente
NULL
no es un valor válido, entonces probablemente debería estar utilizando una referencia en su lugar.Cambiaré tu pregunta ligeramente:
Hay dos escenarios en los que se puede omitir establecer el puntero en NULL:
Mientras tanto, argumentar que establecer el puntero en NULL podría ocultarme errores suena como argumentar que no debe corregir un error porque la solución podría ocultar otro error. Los únicos errores que podrían aparecer si el puntero no está configurado en NULL serían los que intentan usar el puntero. Pero establecerlo en NULL en realidad causaría exactamente el mismo error que se mostrará si lo usa con memoria liberada, ¿no?
fuente
Si no tiene otra restricción que lo obligue a establecer o no establecer el puntero en NULL después de eliminarlo (una de esas restricciones fue mencionada por Neil Butterworth ), entonces mi preferencia personal es dejarlo así.
Para mí, la pregunta no es "¿es una buena idea?" pero "¿qué comportamiento evitaría o permitiría tener éxito al hacer esto?" Por ejemplo, si esto permite que otro código vea que el puntero ya no está disponible, ¿por qué otro código incluso intenta mirar los punteros liberados después de liberarlos? Por lo general, es un error.
También realiza más trabajo del necesario, además de obstaculizar la depuración post mortem. Cuanto menos toque la memoria después de que no la necesite, más fácil será descubrir por qué algo falló. Muchas veces me he basado en el hecho de que la memoria está en un estado similar al de un error en particular para diagnosticar y corregir dicho error.
fuente
La anulación explícita después de eliminar sugiere fuertemente a un lector que el puntero representa algo que es conceptualmente opcional . Si veo que se está haciendo eso, comenzaría a preocuparme de que en todas partes en la fuente que se utiliza el puntero se pruebe primero con NULL.
Si eso es lo que realmente quiere decir, es mejor hacerlo explícito en la fuente usando algo como boost :: opcional
Pero si realmente quisieras que la gente supiera que el puntero ha "ido mal", estaré totalmente de acuerdo con aquellos que dicen que lo mejor que puedes hacer es dejarlo fuera de alcance. Luego está utilizando el compilador para evitar la posibilidad de malas desreferencias en tiempo de ejecución.
Ese es el bebé en toda el agua de baño C ++, no debería tirarlo. :)
fuente
En un programa bien estructurado con verificación de errores adecuada, no hay razón para no asignarlo como nulo.
0
se destaca solo como un valor inválido universalmente reconocido en este contexto. Falla duro y falla pronto.Muchos de los argumentos en contra de la asignación
0
sugieren que podría ocultar un error o complicar el flujo de control. Básicamente, se trata de un error ascendente (no es su culpa (perdón por el mal juego de palabras)) u otro error en nombre del programador, tal vez incluso una indicación de que el flujo del programa se ha vuelto demasiado complejo.Si el programador quiere introducir el uso de un puntero que puede ser nulo como un valor especial y escribir todo lo necesario para evitarlo, esa es una complicación que han introducido deliberadamente. Cuanto mejor sea la cuarentena, antes encontrará casos de uso indebido y menos podrán propagarse a otros programas.
Los programas bien estructurados pueden diseñarse utilizando características de C ++ para evitar estos casos. Puede usar referencias, o simplemente puede decir "pasar / usar argumentos nulos o inválidos es un error", un enfoque que es igualmente aplicable a los contenedores, como los punteros inteligentes. El aumento del comportamiento constante y correcto prohíbe que estos errores lleguen lejos.
A partir de ahí, solo tiene un alcance y contexto muy limitados donde puede existir un puntero nulo (o está permitido).
Lo mismo puede aplicarse a los punteros que no lo son
const
. Seguir el valor de un puntero es trivial porque su alcance es muy pequeño y el uso incorrecto está verificado y bien definido. Si su conjunto de herramientas e ingenieros no pueden seguir el programa después de una lectura rápida o si hay una verificación de error inapropiada o un flujo de programa inconsistente / indulgente, tiene otros problemas mayores.Finalmente, su compilador y su entorno probablemente tengan algunas protecciones para los momentos en que le gustaría introducir errores (garabatear), detectar accesos a la memoria liberada y capturar otros UB relacionados. También puede introducir diagnósticos similares en sus programas, a menudo sin afectar los programas existentes.
fuente
Déjame expandir lo que ya has puesto en tu pregunta.
Esto es lo que ha puesto en su pregunta, en forma de viñeta:
Establecer punteros en NULL después de eliminar no es una buena práctica universal en C ++. Hay momentos en que:
Sin embargo, ¡no hay momentos en que esto sea malo ! Usted no introducir más errores por anulando de forma explícita, no tendrá fugas de memoria, usted no causa un comportamiento indefinido a suceder.
Entonces, en caso de duda, simplemente anúlelo.
Dicho esto, si siente que tiene que anular explícitamente algún puntero, me parece que no ha dividido un método lo suficiente, y debería considerar el enfoque de refactorización llamado "Método de extracción" para dividir el método en partes separadas.
fuente
Si.
El único "daño" que puede hacer es introducir ineficiencia (una operación de almacenamiento innecesaria) en su programa, pero esta sobrecarga será insignificante en relación con el costo de asignar y liberar el bloque de memoria en la mayoría de los casos.
Si no lo hace, usted va a tener algunos errores derefernce puntero desagradables un día.
Siempre uso una macro para eliminar:
(y similar para una matriz, free (), tiradores de liberación)
También puede escribir métodos de "eliminación automática" que tomen una referencia al puntero del código de llamada, de modo que fuercen el puntero del código de llamada a NULL. Por ejemplo, para eliminar un subárbol de muchos objetos:
editar
Sí, estas técnicas violan algunas reglas sobre el uso de macros (y sí, en estos días probablemente podría lograr el mismo resultado con las plantillas), pero al usarlas durante muchos años nunca he accedido a la memoria muerta, una de las más desagradables y difíciles La mayor parte del tiempo para depurar los problemas que puede enfrentar. En la práctica, durante muchos años, han eliminado eficazmente una gran variedad de errores de cada equipo en el que los he presentado.
También hay muchas maneras de implementar lo anterior: solo estoy tratando de ilustrar la idea de obligar a las personas a anular un puntero si eliminan un objeto, en lugar de proporcionarles un medio para liberar la memoria que no anula el puntero de la persona que llama. .
Por supuesto, el ejemplo anterior es solo un paso hacia un puntero automático. Lo cual no sugerí porque el OP estaba preguntando específicamente sobre el caso de no usar un puntero automático.
fuente
anObject->Delete(anObject)
invalidar elanObject
puntero. Eso es aterrador. Debe crear un método estático para esto, de modo que se vea obligado a hacerloTreeItem::Delete(anObject)
al menos."Hay momentos en los que es bueno hacerlo, y momentos en los que no tiene sentido y puede ocultar errores"
Puedo ver dos problemas: Ese código simple:
se convierte en un for-liner en un entorno multiproceso:
La "mejor práctica" de Don Neufeld no se aplica siempre. Por ejemplo, en un proyecto automotriz tuvimos que establecer punteros a 0 incluso en destructores. Me imagino que en el software crítico para la seguridad tales reglas no son infrecuentes. Es más fácil (y sabio) seguirlos que tratar de persuadir al equipo / verificador de código para cada uso de puntero en el código, de que una línea que anula este puntero es redundante.
Otro peligro es confiar en esta técnica en el código de uso de excepciones:
En dicho código, usted produce pérdida de recursos y pospone el problema o el proceso se bloquea.
Entonces, estos dos problemas que pasan por mi cabeza espontáneamente (Herb Sutter seguramente diría más) me hacen todas las preguntas del tipo "Cómo evitar el uso de punteros inteligentes y hacer el trabajo de manera segura con punteros normales" como obsoletos.
fuente
Siempre hay que preocuparse por los Punteros colgantes .
fuente
Si va a reasignar el puntero antes de usarlo nuevamente (desreferenciarlo, pasarlo a una función, etc.), hacer que el puntero sea NULL es solo una operación adicional. Sin embargo, si no está seguro de si se reasignará o no antes de volver a usarlo, es una buena idea establecerlo en NULL.
Como muchos han dicho, es mucho más fácil usar punteros inteligentes.
Editar: como dijo Thomas Matthews en esta respuesta anterior , si un puntero se elimina en un destructor, no hay necesidad de asignarle NULL ya que no se volverá a usar porque el objeto ya se está destruyendo.
fuente
Me imagino que configurar un puntero en NULL después de eliminarlo es útil en casos excepcionales en los que existe un escenario legítimo de reutilizarlo en una sola función (u objeto). De lo contrario, no tiene sentido, un puntero necesita señalar algo significativo mientras exista, punto.
fuente
Si el código no pertenece a la parte más crítica del rendimiento de su aplicación, manténgalo simple y use shared_ptr:
Realiza el recuento de referencias y es seguro para subprocesos. Puede encontrarlo en el tr1 (espacio de nombres std :: tr1, #include <memoria>) o si su compilador no lo proporciona, obténgalo de boost.
fuente