Si necesito usar un trozo de memoria durante la vida útil de mi programa, ¿es realmente necesario liberarlo justo antes de la finalización del programa?

67

En muchos libros y tutoriales, he escuchado la práctica del manejo de la memoria enfatizada y sentí que sucederían algunas cosas misteriosas y terribles si no liberaba la memoria después de terminar de usarla.

No puedo hablar por otros sistemas (aunque para mí es razonable suponer que adoptan una práctica similar), pero al menos en Windows, el Kernel básicamente garantiza la limpieza de la mayoría de los recursos (con la excepción de unos pocos) utilizados por un programa después de la terminación del programa. Que incluye memoria de montón, entre varias otras cosas.

Entiendo por qué querrías cerrar un archivo una vez que hayas terminado de usarlo para que esté disponible para el usuario o por qué quieres desconectar un socket conectado a un servidor para ahorrar ancho de banda, pero parece una tontería tiene que micro administrar TODA su memoria utilizada por su programa.

Ahora, estoy de acuerdo en que esta pregunta es amplia, ya que la forma en que debe manejar su memoria se basa en la cantidad de memoria que necesita y cuándo la necesita, por lo que limitaré el alcance de esta pregunta a esto: si necesito usar un trozo de memoria memoria a lo largo de la vida útil de mi programa, ¿es realmente necesario liberarla justo antes de la finalización del programa?

Editar: La pregunta sugerida como duplicado era específica de la familia de sistemas operativos Unix. Su respuesta principal incluso especificaba una herramienta específica para Linux (por ejemplo, Valgrind). Esta pregunta está destinada a cubrir la mayoría de los sistemas operativos no integrados "normales" y por qué es o no una buena práctica liberar memoria que se necesita durante la vida útil de un programa.

Capitán Obvio
fuente
19
Ha etiquetado este idioma de manera independiente, pero en muchos idiomas (por ejemplo, java) no es necesario liberar memoria manualmente; sucede automáticamente un tiempo después de que la última referencia a un objeto se salga del alcance
Richard Tingle
3
y, por supuesto, puedes escribir C ++ sin borrar nada y estaría bien para la memoria
Nikko
55
@RichardTingle Si bien no puedo pensar en ningún otro lenguaje fuera de mi cabeza que no sea C y tal vez C ++, la etiqueta independiente del idioma estaba destinada a cubrir todos los idiomas que no tienen muchas utilidades de recolección de basura integradas. Sin embargo, eso es ciertamente raro hoy en día. Podría argumentar que ahora puede implementar dicho sistema en C ++, pero aún tiene la opción de no eliminar una pieza de memoria.
CaptainObvious
44
Usted escribe: "el núcleo está básicamente garantizado para limpiar todos los recursos [...] después de la finalización del programa". Esto generalmente es falso, porque no todo es memoria, un identificador o un objeto kernel. Pero es cierto para la memoria, a la que inmediatamente restringe su pregunta.
Eric Towers

Respuestas:

108

Si necesito usar un trozo de memoria durante la vida útil de mi programa, ¿es realmente necesario liberarlo justo antes de la finalización del programa?

No es obligatorio, pero puede tener beneficios (así como algunos inconvenientes).

Si el programa asigna memoria una vez durante su tiempo de ejecución, y de lo contrario nunca la liberaría hasta que finalice el proceso, puede ser un enfoque sensato no liberar la memoria manualmente y confiar en el sistema operativo. En todos los sistemas operativos modernos que conozco, esto es seguro, al final del proceso, toda la memoria asignada se devuelve de manera confiable al sistema.
En algunos casos, no limpiar explícitamente la memoria asignada puede ser notablemente más rápido que hacer la limpieza.

Sin embargo, al liberar explícitamente toda la memoria al final de la ejecución,

  • durante la depuración / prueba, las herramientas de detección de fugas de memoria no le mostrarán "falsos positivos"
  • puede ser mucho más fácil mover el código que usa la memoria junto con la asignación y la desasignación a un componente separado y usarlo más tarde en un contexto diferente donde el usuario del componente debe controlar el tiempo de uso de la memoria

La vida útil de los programas puede cambiar. Quizás su programa sea una pequeña utilidad de línea de comandos hoy en día, con una vida útil típica de menos de 10 minutos, y asigne memoria en porciones de algunos kb cada 10 segundos, por lo que no es necesario liberar ninguna memoria asignada antes de que finalice el programa. Más adelante, el programa cambia y obtiene un uso extendido como parte de un proceso de servidor con una vida útil de varias semanas, por lo que no liberar más memoria no utilizada ya no es una opción, de lo contrario, su programa comenzará a consumir toda la memoria del servidor disponible con el tiempo . Esto significa que tendrá que revisar todo el programa y agregar código de asignación después. Si tiene suerte, esta es una tarea fácil, si no, puede ser tan difícil que hay muchas posibilidades de que pierda un lugar. Y cuando se encuentre en esa situación, deseará haber agregado la opción "gratuita"

En términos más generales, la escritura de asignación y el código de desasignación relacionado siempre cuenta por pares como un "buen hábito" entre muchos programadores: al hacer esto siempre, disminuye la probabilidad de olvidar el código de desasignación en situaciones en las que se debe liberar la memoria .

Doc Brown
fuente
12
Buen punto sobre el desarrollo de buenos hábitos de programación.
Lawrence
44
En general, estoy totalmente de acuerdo con este consejo. Mantener buenos hábitos con la memoria es muy importante. Sin embargo, creo que hay excepciones. Por ejemplo, si lo que ha asignado es un gráfico loco que llevará varios segundos atravesar y liberar "correctamente", entonces estaría mejorando la experiencia del usuario simplemente finalizando el programa y dejando que el sistema operativo lo destruya.
GrandOpener
1
Es seguro en todos los sistemas operativos "normales" modernos (no integrados con protección de memoria) y sería un error grave si no lo fuera. Si un proceso sin privilegios puede perder permanentemente memoria que el sistema operativo no reclamará, entonces ejecutarlo muchas veces puede ejecutar el sistema sin memoria. La salud a largo plazo del sistema operativo no puede depender de la falta de errores en los programas no privilegiados. (por supuesto, esto es ignorar cosas como Unix segmentos de memoria compartida, que sólo están respaldados por espacio de intercambio en el mejor.)
Peter Cordes
77
@GrandOpener En este caso, es posible que prefiera usar algún tipo de asignador basado en regiones para este árbol, de modo que pueda asignarlo de la manera normal y simplemente desasignar toda la región de una vez cuando llegue el momento, en lugar de atravesarla y liberarla poco a poco Eso sigue siendo "correcto".
Thomas
3
Además, si la memoria realmente necesita existir durante toda la vida útil del programa, puede ser una alternativa sensata crear una estructura en la pila, por ejemplo, en main.
Kyle Strand
11

Liberar memoria al final de la ejecución de un programa es solo una pérdida de tiempo de CPU. Es como ordenar una casa antes de destruirla desde la órbita.

Sin embargo, a veces lo que era un programa de ejecución corta puede convertirse en parte de un programa de ejecución mucho más largo. Entonces liberar cosas se hace necesario. Si esto no se pensó al menos hasta cierto punto, puede implicar una reelaboración sustancial.

Una solución inteligente para esto es "talloc", que le permite realizar una carga de asignaciones de memoria y luego tirarlas todas con una sola llamada.

Peter Green
fuente
1
Suponiendo que sabe que su sistema operativo no tiene sus propias fugas.
WGroleau
55
"pérdida de tiempo de CPU" Aunque es una cantidad de tiempo muy, muy pequeña. freesuele ser mucho más rápido que malloc.
Paul Draper
@PaulDraper Sí, la pérdida de tiempo para volver a cambiar todo es mucho más importante.
Deduplicador
5

Puede usar un lenguaje con recolección de basura (como Scheme, Ocaml, Haskell, Common Lisp e incluso Java, Scala, Clojure).

(¡En la mayoría de los lenguajes GC-ed, no hay forma de liberar memoria explícita y manualmente ! A veces, algunos valores pueden finalizarse , por ejemplo, el GC y el sistema de tiempo de ejecución cerrarían un valor de identificador de archivo cuando ese valor sea inalcanzable; pero esto no es seguro, poco confiable, y en su lugar debe cerrar explícitamente sus identificadores de archivo, ya que la finalización nunca está garantizada)

También podría, para su programa codificado en C (o incluso C ++) usar el recolector de basura conservador de Boehm . A continuación, reemplazaría todo malloccon GC_malloc y no se molestaría en hacer freeclic en ningún puntero. Por supuesto, debe comprender los pros y los contras del uso de GC de Boehm. Lea también el manual de GC .

La gestión de memoria es una propiedad global de un programa. De alguna manera (y la vitalidad de algunos datos dados) no es compositiva ni modular, ya que es una propiedad completa del programa.

Por último, como otros señalaron, freees una buena práctica hacer explícitamente su zona de memoria C asignada en el montón. Para un programa de juguete que no asigna mucha memoria, incluso podría decidir no tener freememoria (ya que cuando el proceso haya finalizado, sus recursos, incluido su espacio de direcciones virtuales , serían liberados por el sistema operativo).

Si necesito usar un trozo de memoria durante la vida útil de mi programa, ¿es realmente necesario liberarlo justo antes de la finalización del programa?

No, no necesitas hacer eso. Y muchos programas del mundo real no se molestan en liberar algo de memoria que se necesita durante toda la vida útil (en particular, el compilador GCC no está liberando parte de su memoria). Sin embargo, cuando lo haga (por ejemplo, no se moleste en freeextraer una parte particular de datos asignados dinámicamente en C ), será mejor que comente ese hecho para facilitar el trabajo de futuros programadores en el mismo proyecto. Tiendo a recomendar que la cantidad de memoria no liberada permanezca acotada, y usualmente es relativamente pequeña wrt al total de memoria heap usada.

Tenga en cuenta que el sistema a freemenudo no libera memoria al sistema operativo (por ejemplo, llamando a munmap (2) en los sistemas POSIX), pero generalmente marca una zona de memoria como reutilizable en el futuro malloc. En particular, el espacio de direcciones virtuales (por ejemplo, como se ve /proc/self/mapsen Linux, ver proc (5) ...) podría no reducirse free(por lo tanto, las utilidades les gusta pso topinforman la misma cantidad de memoria utilizada para su proceso).

Basile Starynkevitch
fuente
3
"A veces, algunos valores pueden finalizarse, por ejemplo, el GC y el sistema de tiempo de ejecución cerrarían un valor de identificador de archivo cuando ese valor no se pueda alcanzar". cualquier finalizador que se haya ejecutado, incluso al salir: stackoverflow.com/questions/7880569/…
Deduplicator
Personalmente prefiero esta respuesta, ya que nos anima a usar GC (es decir, un enfoque más automatizado).
Ta Thanh Dinh
3
@tathanhdinh Más automatizado, aunque exclusivamente para memoria. Completamente manual, para todos los demás recursos. GC funciona intercambiando determinismo y memoria para un manejo conveniente de la memoria. Y no, los finalizadores no ayudan mucho y tienen sus propios problemas.
Deduplicador
3

No es necesario , ya que no podrá ejecutar su programa correctamente si no lo hace. Sin embargo, hay razones por las que puede elegir, si tiene la oportunidad.

Uno de los casos más poderosos con los que me encuentro (una y otra vez) es que alguien escribe un pequeño código de simulación que se ejecuta en su ejecutable. Dicen "nos gustaría que este código se integre en la simulación". Luego les pregunto cómo planean reinicializar entre las carreras de monte-carlo, y me miran sin comprender. "¿Qué quieres decir con reiniciar? ¿Acabas de ejecutar el programa con la nueva configuración?"

A veces, una limpieza limpia facilita mucho el uso de su software. En el caso de muchos ejemplos, presume que nunca necesita limpiar algo, y hace suposiciones sobre cómo puede manejar los datos y su vida útil en torno a esas presunciones. Cuando se muda a un nuevo entorno donde esas presunciones no son válidas, es posible que algoritmos completos ya no funcionen.

Para ver un ejemplo de cuán extrañas pueden ser las cosas, observe cómo los lenguajes administrados se encargan de la finalización al final de los procesos, o cómo C # se ocupa de la detención programática de los dominios de aplicación. Se atan en nudos porque hay suposiciones que caen por las grietas en estos casos extremos.

Cort Ammon
fuente
1

Ignorando los idiomas donde no liberas memoria manualmente de todos modos ...

Lo que usted piensa como "un programa" en este momento podría convertirse en una función o método que forma parte de un programa más grande. Y luego esa función podría llamarse varias veces. Y luego, la memoria que debería haber "liberado manualmente" será una pérdida de memoria. Eso es, por supuesto, una decisión judicial.

gnasher729
fuente
2
esto parece simplemente repetir el punto hecho (y mucho mejor explicado) en la respuesta superior que se publicó unas horas antes: "podría ser mucho más fácil mover el código que usa la memoria junto con la asignación y desasignación en un componente separado y usarlo más tarde en un contexto diferente donde el tiempo de uso de la memoria debe ser controlado por el usuario del componente ... "
gnat
1

Debido a que es muy probable que en algún momento quieras alterar tu programa, quizás integrarlo con otra cosa, ejecutar varias instancias en secuencia o en paralelo. Entonces será necesario liberar manualmente esta memoria, pero ya no recordará las circunstancias y le costará mucho más tiempo volver a comprender su programa.

Haz las cosas mientras tu comprensión sobre ellas todavía es fresca.

Es una pequeña inversión que puede generar grandes ganancias en el futuro.

Agent_L
fuente
2
Esto no parece agregar nada sustancial sobre los puntos hechos y explicados en las 11 respuestas anteriores. En particular, el punto sobre la posibilidad de alterar el programa en el futuro se ha hecho tres o cuatro veces ya
mosquito
1
@gnat De una manera enrevesada y oculta, sí. En una declaración clara: no. Centrémonos en la calidad de la respuesta, no en la rapidez.
Agent_L
1
En cuanto a la calidad, la mejor respuesta parece ser mucho mejor para explicar este punto
mosquito
1

No, no es necesario, pero es una buena idea.

Dices que sientes que "sucederían cosas misteriosas y terribles si no liberara memoria después de que termine de usarlo".

En términos técnicos, la única consecuencia de esto es que su programa seguirá consumiendo más memoria hasta que se alcance un límite difícil (por ejemplo, su espacio de direcciones virtuales se agote) o el rendimiento se vuelva inaceptable. Si el programa está a punto de salir, nada de esto importa porque el proceso efectivamente deja de existir. Las "cosas misteriosas y terribles" son puramente sobre el estado mental del desarrollador. Encontrar la fuente de una pérdida de memoria puede ser una pesadilla absoluta (esto es un eufemismo) y se necesita mucha habilidad y disciplina para escribir código que no tenga pérdidas. El enfoque recomendado para desarrollar esta habilidad y disciplina es liberar siempre la memoria una vez que ya no sea necesaria, incluso si el programa está a punto de finalizar.

Por supuesto, esto tiene la ventaja adicional de que su código puede reutilizarse y adaptarse más fácilmente, como han dicho otros.

Sin embargo , hay al menos un caso en el que es mejor no liberar memoria justo antes de la finalización del programa.

Considere el caso en el que ha realizado millones de pequeñas asignaciones, y en su mayoría se han cambiado al disco. Cuando comienzas a liberar todo, la mayor parte de tu memoria debe volver a cambiarse a RAM para que se pueda acceder a la información de contabilidad, solo para descartar inmediatamente los datos. ¡Esto puede hacer que el programa tarde unos minutos en salir! Sin mencionar que ejerció mucha presión sobre el disco y la memoria física durante ese tiempo. Si la memoria física es escasa para empezar (tal vez el programa se está cerrando porque otro programa está masticando mucha memoria), entonces es posible que sea necesario intercambiar páginas individuales varias veces cuando sea necesario liberar varios objetos del programa. misma página, pero no se liberan consecutivamente.

Si, en cambio, el programa simplemente aborta, el sistema operativo simplemente descartará toda la memoria que se ha intercambiado al disco, lo cual es casi instantáneo porque no requiere ningún acceso al disco.

Es importante tener en cuenta que en un lenguaje OO, llamar al destructor de un objeto también forzará el intercambio de memoria; si tiene que hacer esto, también podría liberar la memoria.

Artelius
fuente
1
¿Puedes citar algo para apoyar la idea de que la memoria paginada debe ser paginada para desasignar? Eso es obviamente ineficiente, ¡seguramente los sistemas operativos modernos son más inteligentes que eso!
1
@Jon of All Trades, los sistemas operativos modernos son inteligentes, pero irónicamente la mayoría de los lenguajes modernos no lo son. Cuando libera (algo), el compilador colocará "algo" en la pila y luego llamará a free (). Ponerlo en la pila requiere cambiarlo de nuevo a la RAM si se cambia.
Jeffiekins
@JonofAllTrades una lista libre tendría ese efecto, aunque es una implementación malloc bastante obsoleta. Pero el sistema operativo no puede evitar que use dicha implementación.
0

Las principales razones para limpiar manualmente son: es menos probable que confunda una pérdida de memoria genuina con un bloque de memoria que se limpiará al salir, a veces detectará errores en el asignador solo cuando recorra toda la estructura de datos para desasignar él, y usted tiene un dolor de cabeza mucho menor si alguna vez refactoriza modo que un objeto no necesita para obtener desasignado antes de salir del programa.

Las principales razones para no realizar la limpieza manualmente son: rendimiento, rendimiento, rendimiento y la posibilidad de algún tipo de error free-two o use-after-free en su código de limpieza innecesario, que puede convertirse en un error o seguridad explotar.

Si le importa el rendimiento, siempre desea crear un perfil para descubrir dónde está malgastando todo su tiempo, en lugar de adivinar. Un posible compromiso es envolver su código de limpieza opcional en un bloque condicional, dejarlo al depurar para obtener los beneficios de escribir y depurar el código, y luego, si y solo si ha determinado empíricamente que es demasiado arriba, dígale al compilador que lo omita en su ejecutable final. Y un retraso en el cierre del programa, casi por definición, casi nunca se encuentra en la ruta crítica.

Davislor
fuente