Esto ha sido algo que me ha molestado por años.
A todos nos enseñan en la escuela (al menos, yo era) que DEBES liberar cada puntero asignado. Sin embargo, tengo un poco de curiosidad sobre el costo real de no liberar memoria. En algunos casos obvios, como cuando malloc
se llama dentro de un bucle o parte de una ejecución de subproceso, es muy importante liberar para que no haya pérdidas de memoria. Pero considere los siguientes dos ejemplos:
Primero, si tengo código, es algo como esto:
int main()
{
char *a = malloc(1024);
/* Do some arbitrary stuff with 'a' (no alloc functions) */
return 0;
}
¿Cuál es el resultado real aquí? Mi opinión es que el proceso muere y luego el espacio de almacenamiento dinámico desaparece de todos modos, por lo que no hay nada de malo en perder la llamada free
(sin embargo, reconozco la importancia de tenerlo de todos modos para el cierre, la mantenibilidad y las buenas prácticas). ¿Estoy en lo cierto en este pensamiento?
Segundo, digamos que tengo un programa que actúa un poco como un shell. Los usuarios pueden declarar variables como aaa = 123
y se almacenan en alguna estructura de datos dinámicos para su uso posterior. Claramente, parece obvio que usaría alguna solución que invocará alguna función * alloc (hashmap, lista vinculada, algo así). Para este tipo de programa, no tiene sentido liberarse nunca después de llamar malloc
porque estas variables deben estar presentes en todo momento durante la ejecución del programa y no hay una buena manera (que pueda ver) de implementar esto con espacio estáticamente asignado. ¿Es un mal diseño tener un montón de memoria asignada pero solo liberada como parte del final del proceso? Si es así, ¿cuál es la alternativa?
free(a)
realidad no hace nada para liberar memoria! Simplemente restablece algunos punteros en la implementación libc de malloc que realizan un seguimiento de los fragmentos de memoria disponibles dentro de una gran página de memoria mmapped (comúnmente llamada el "montón"). Esa página solo se liberará cuando finalice su programa, no antes.Respuestas:
Casi todos los sistemas operativos modernos recuperarán todo el espacio de memoria asignado después de que un programa salga. La única excepción que se me ocurre podría ser algo así como Palm OS, donde el almacenamiento estático del programa y la memoria de tiempo de ejecución son más o menos lo mismo, por lo que no liberarlo podría hacer que el programa tome más espacio de almacenamiento. (Solo estoy especulando aquí).
Por lo tanto, en general, no hay daño, excepto el costo de tiempo de ejecución de tener más almacenamiento del que necesita. Ciertamente, en el ejemplo que da, desea mantener la memoria para una variable que podría usarse hasta que se borre.
Sin embargo, se considera un buen estilo liberar memoria tan pronto como ya no la necesite, y liberar todo lo que todavía tiene al salir del programa. Es más un ejercicio para saber qué memoria estás usando y pensar si aún la necesitas. Si no realiza un seguimiento, es posible que tenga pérdidas de memoria.
Por otro lado, la advertencia similar para cerrar sus archivos al salir tiene un resultado mucho más concreto: si no lo hace, los datos que les escribió podrían no vaciarse, o si son un archivo temporal, podrían no ser eliminado cuando hayas terminado. Además, los identificadores de la base de datos deben tener sus transacciones confirmadas y luego cerrarse cuando haya terminado con ellas. Del mismo modo, si está utilizando un lenguaje orientado a objetos como C ++ u Objective C, no liberar un objeto cuando haya terminado significará que nunca se llamará al destructor, y los recursos de los que es responsable la clase podrían no limpiarse.
fuente
Sí, tiene razón, su ejemplo no hace ningún daño (al menos no en la mayoría de los sistemas operativos modernos). Toda la memoria asignada por su proceso será recuperada por el sistema operativo una vez que el proceso finalice.
Fuente: Mitos de asignación y GC (¡alerta de PostScript!)
Dicho esto, ¡realmente deberías tratar de evitar todas las pérdidas de memoria!
Segunda pregunta: tu diseño está bien. Si necesita almacenar algo hasta que su aplicación salga, entonces está bien hacerlo con asignación de memoria dinámica. Si no conoce el tamaño requerido por adelantado, no puede usar memoria asignada estáticamente.
fuente
=== ¿Qué pasa con las pruebas futuras y la reutilización de código ? ===
Si no escribe el código para liberar los objetos, entonces está limitando el código para que solo sea seguro de usar cuando puede depender de que la memoria se libere al cerrar el proceso ... es decir, un pequeño uso de una sola vez proyectos o proyectos "desechables" [1] ) ... donde se sabe cuándo terminará el proceso.
Si haces escribir el código que el libre () s de toda la memoria asignada dinámicamente, entonces usted está a prueba de futuro el código y dejar que otros lo utilizan en un proyecto más amplio.
[1] con respecto a proyectos "desechables". El código utilizado en proyectos "desechables" tiene una forma de no desecharse. Lo siguiente que sabes es que han pasado diez años y tu código de "descarte" todavía se está usando).
Escuché una historia sobre un tipo que escribió un código solo por diversión para hacer que su hardware funcione mejor. Él dijo " solo un hobby, no será grande y profesional ". Años más tarde, muchas personas están usando su código de "pasatiempo".
fuente
malloc()
si es así y termina si el puntero sigue siendo nulo, dicha función se puede usar de forma segura un número arbitrario de veces, incluso sifree
nunca se llama. Creo que probablemente valga la pena distinguir las pérdidas de memoria que pueden consumir una cantidad ilimitada de almacenamiento, frente a situaciones que solo pueden desperdiciar una cantidad finita y predecible de almacenamiento.null
si no existe una asignación, y no es nulo cuando existe una asignación, tener un código libre de la asignación y establecer el puntero paranull
cuando se destruye un contexto sería sencillo, especialmente en comparación con todo lo demás que debería hacerse para mover objetos estáticos a una estructura de contexto.Estás en lo correcto, no se hace daño y es más rápido salir
Hay varias razones para esto:
Todos los entornos de escritorio y servidor simplemente liberan todo el espacio de memoria al salir (). No conocen las estructuras de datos internas del programa, como los montones.
Casi todas las
free()
implementaciones nunca devuelven memoria al sistema operativo de todos modos.Más importante aún, es una pérdida de tiempo cuando se hace justo antes de la salida (). Al salir, las páginas de memoria y el espacio de intercambio simplemente se liberan. Por el contrario, una serie de llamadas gratuitas () quemarán el tiempo de CPU y pueden provocar operaciones de paginación de disco, fallas de caché y desalojos de caché.
En cuanto a la posibilidad de reutilización de código futuro justificando la certeza de operaciones sin sentido: eso es una consideración, pero posiblemente no sea la forma ágil . YAGNI!
fuente
Estoy completamente en desacuerdo con todos los que dicen que OP es correcto o no hay daño.
Todos están hablando de un sistema operativo moderno y / o heredado.
Pero, ¿qué pasa si estoy en un entorno donde simplemente no tengo SO? Donde no hay nada?
Imagínese ahora que está utilizando interrupciones con estilo de hilo y asigna memoria. En el estándar C ISO / IEC: 9899 es la vida útil de la memoria indicada como:
Por lo tanto, no debe darse por sentado que el medio ambiente está haciendo el trabajo de liberación por usted. De lo contrario, se agregaría a la última oración: "O hasta que el programa finalice".
En otras palabras: no liberar memoria no es solo una mala práctica. Produce código no portátil y no conforme a C. Lo que al menos puede verse como "correcto, si lo siguiente: [...], es compatible con el entorno".
Pero en los casos en que no tiene ningún sistema operativo, nadie está haciendo el trabajo por usted (sé que generalmente no asigna y reasigna la memoria en los sistemas integrados, pero hay casos en los que es posible que desee).
Hablando en general, en forma de C simple (como se etiqueta el OP), esto simplemente está produciendo código erróneo y no portátil.
fuente
Por lo general, libero cada bloque asignado una vez que estoy seguro de que he terminado con él. Hoy, el punto de entrada de mi programa podría ser
main(int argc, char *argv[])
, pero mañana podría serfoo_entry_point(char **args, struct foo *f)
y tecleado como un puntero de función.Entonces, si eso sucede, ahora tengo una fuga.
Con respecto a su segunda pregunta, si mi programa tomara datos como a = 5, asignaría espacio para a, o reasignaría el mismo espacio en un a = "foo" posterior. Esto permanecería asignado hasta que:
No puedo pensar en ningún sistema operativo moderno que no recupere memoria después de que un proceso finalice. Por otra parte, free () es barato, ¿por qué no limpiar? Como han dicho otros, las herramientas como valgrind son excelentes para detectar fugas de las que realmente debe preocuparse. Aunque los bloques que muestra se etiquetarían como 'todavía accesibles', es solo ruido adicional en la salida cuando intenta asegurarse de que no haya fugas.
Otro mito es " Si está en main (), no tengo que liberarlo ", esto es incorrecto. Considera lo siguiente:
Si eso ocurrió antes de bifurcar / demonizar (y en teoría se ejecuta para siempre), su programa acaba de filtrar un tamaño indeterminado de t 255 veces.
Un buen programa bien escrito siempre debe limpiarse después de sí mismo. Libere toda la memoria, vacíe todos los archivos, cierre todos los descriptores, desvincule todos los archivos temporales, etc. Esta función de limpieza debe alcanzarse después de la terminación normal o al recibir varios tipos de señales fatales, a menos que desee dejar algunos archivos por ahí para que pueda detectar un bloqueo y reanudar
Realmente, sé amable con la pobre alma que tiene que mantener tus cosas cuando pasas a otras cosas ... entrégalas 'valgrind clean' :)
fuente
free() is cheap
a menos que tenga un billón de estructuras de datos con una relación compleja que tiene que liberar una por una, atravesar la estructura de datos para intentar liberar todo podría terminar aumentando significativamente su tiempo de cierre, especialmente si la mitad de esa estructura de datos ya está paginada al disco, sin ningún beneficio.Está completamente bien dejar memoria sin liberar cuando salga; malloc () asigna la memoria del área de memoria llamada "el montón", y el montón completo de un proceso se libera cuando el proceso sale.
Dicho esto, una razón por la cual la gente todavía insiste en que es bueno liberar todo antes de salir es que los depuradores de memoria (por ejemplo, valgrind en Linux) detectan los bloques no alimentados como pérdidas de memoria, y si también tiene pérdidas de memoria "reales", se convierte en es más difícil detectarlos si también obtienes resultados "falsos" al final.
fuente
free
en elexit
momento considerado perjudicial.Si está utilizando la memoria que ha asignado, entonces no está haciendo nada malo. Se convierte en un problema cuando escribe funciones (que no sean main) que asignan memoria sin liberarla y sin ponerla a disposición del resto de su programa. Luego, su programa continúa ejecutándose con esa memoria asignada, pero no hay forma de usarlo. Su programa y otros programas en ejecución se ven privados de esa memoria.
Editar: no es 100% exacto decir que otros programas en ejecución están privados de esa memoria. El sistema operativo siempre puede permitirles usarlo a expensas de intercambiar su programa a la memoria virtual (
</handwaving>
). Sin embargo, el punto es que si su programa libera memoria que no está utilizando, es menos probable que sea necesario un intercambio de memoria virtual.fuente
Este código generalmente funcionará bien, pero considere el problema de la reutilización del código.
Es posible que haya escrito un fragmento de código que no libera memoria asignada, se ejecuta de tal manera que la memoria se recupera automáticamente. Parece bien
Luego, alguien más copia su fragmento en su proyecto de tal manera que se ejecute mil veces por segundo. Esa persona ahora tiene una gran pérdida de memoria en su programa. No muy bueno en general, generalmente fatal para una aplicación de servidor.
La reutilización del código es típica en las empresas. Por lo general, la empresa posee todo el código que producen sus empleados y cada departamento puede reutilizar lo que sea que posea la empresa. Entonces, al escribir un código de "aspecto inocente", puede causar dolor de cabeza a otras personas. Esto puede hacerte despedir.
fuente
Su programa filtró la memoria. Dependiendo de su sistema operativo, puede haberse recuperado.
La mayoría de los modernos de escritorio sistemas operativos hacen recuperar la memoria perdida en la terminación del proceso, por lo que es tristemente común a ignorar el problema, como puede verse por muchas otras respuestas aquí.)
Pero usted está confiando en una característica de seguridad no se debe confiar en ella, y su programa (o función) puede ser que funcione en un sistema donde este comportamiento hace resultado de una fuga "duro" de memoria, próxima vez.
Es posible que se esté ejecutando en modo kernel o en sistemas operativos antiguos / integrados que no emplean protección de memoria como compensación. (Las MMU ocupan el espacio del dado, la protección de la memoria cuesta ciclos de CPU adicionales y no es demasiado pedirle a un programador que se limpie después de sí mismo).
Puede usar y reutilizar la memoria de la manera que desee, pero asegúrese de desasignar todos los recursos antes de salir.
fuente
En realidad, hay una sección en el libro de texto en línea de OSTEP para un curso de pregrado en sistemas operativos que trata exactamente su pregunta.
La sección relevante es "Olvidando liberar memoria" en el capítulo API de memoria en la página 6, que da la siguiente explicación:
Este extracto está en el contexto de la introducción del concepto de memoria virtual. Básicamente, en este punto del libro, los autores explican que uno de los objetivos de un sistema operativo es "virtualizar la memoria", es decir, dejar que cada programa crea que tiene acceso a un espacio de direcciones de memoria muy grande.
Detrás de escena, el sistema operativo traducirá "direcciones virtuales" que el usuario ve a direcciones reales que apuntan a la memoria física.
Sin embargo, compartir recursos como la memoria física requiere que el sistema operativo realice un seguimiento de los procesos que lo utilizan. Entonces, si un proceso finaliza, entonces está dentro de las capacidades y los objetivos de diseño del sistema operativo reclamar la memoria del proceso para que pueda redistribuir y compartir la memoria con otros procesos.
EDITAR: el lado mencionado en el extracto se copia a continuación.
fuente
No hay peligro real en no liberar sus variables, pero si asigna un puntero a un bloque de memoria a un bloque de memoria diferente sin liberar el primer bloque, el primer bloque ya no es accesible, pero aún ocupa espacio. Esto es lo que se llama una pérdida de memoria, y si lo hace con regularidad, su proceso comenzará a consumir más y más memoria, eliminando los recursos del sistema de otros procesos.
Si el proceso es de corta duración, a menudo puede salirse con la suya ya que el sistema operativo recupera toda la memoria asignada cuando se completa el proceso, pero le aconsejaría que se acostumbrara a liberar toda la memoria para la que no tiene más uso.
fuente
Tienes toda la razón al respecto. En pequeños programas triviales donde una variable debe existir hasta la muerte del programa, no hay un beneficio real para desasignar la memoria.
De hecho, una vez estuve involucrado en un proyecto donde cada ejecución del programa era muy compleja pero relativamente de corta duración, y la decisión fue simplemente mantener la memoria asignada y no desestabilizar el proyecto cometiendo errores al desasignarlo.
Dicho esto, en la mayoría de los programas esto no es realmente una opción, o puede llevarlo a quedarse sin memoria.
fuente
Tiene razón, la memoria se libera automáticamente cuando el proceso finaliza. Algunas personas se esfuerzan por no hacer una limpieza exhaustiva cuando finaliza el proceso, ya que todo se entregará al sistema operativo. Sin embargo, mientras su programa se está ejecutando, debe liberar memoria no utilizada. Si no lo hace, eventualmente se acabará o causará una paginación excesiva si su conjunto de trabajo se vuelve demasiado grande.
fuente
Si está desarrollando una aplicación desde cero, puede tomar algunas decisiones informadas sobre cuándo llamar gratis. Su programa de ejemplo está bien: asigna memoria, tal vez haga que funcione durante unos segundos y luego se cierre, liberando todos los recursos que reclama.
Sin embargo, si está escribiendo algo más: un servidor / aplicación de larga ejecución o una biblioteca para ser utilizada por otra persona, debe esperar llamar gratis a todo lo que malloc.
Ignorando el lado pragmático por un segundo, es mucho más seguro seguir el enfoque más estricto y obligarse a liberar todo lo que malloc. Si no tiene la costumbre de detectar pérdidas de memoria cada vez que codifica, podría generar fácilmente algunas pérdidas. En otras palabras, sí, puedes escapar sin eso; sin embargo, ten cuidado.
fuente
Si un programa olvida liberar unos pocos megabytes antes de salir, el sistema operativo los liberará. Pero si su programa se ejecuta durante semanas a la vez y un bucle dentro del programa se olvida de liberar algunos bytes en cada iteración, tendrá una gran pérdida de memoria que consumirá toda la memoria disponible en su computadora a menos que la reinicie regularmente base => incluso las pequeñas pérdidas de memoria pueden ser malas si el programa se usa para una tarea realmente grande, incluso si originalmente no fue diseñado para una.
fuente
Creo que sus dos ejemplos son en realidad solo uno:
free()
deberían ocurrir solo al final del proceso, lo cual, como usted señala, es inútil ya que el proceso está terminando.Sin embargo, en su segundo ejemplo, la única diferencia es que permite un número indefinido de
malloc()
, lo que podría provocar la falta de memoria. La única forma de manejar la situación es verificar el código de retornomalloc()
y actuar en consecuencia.fuente