Estoy estudiando ingeniería informática y tengo algunos cursos de electrónica. Oí, de dos de mis profesores (de estos cursos) que es posible evitar el uso de la free()
función (después de malloc()
, calloc()
, etc.), ya que los espacios de memoria asignados probablemente no será utilizada nuevamente para asignar otra memoria. Es decir, por ejemplo, si asigna 4 bytes y luego los libera, tendrá 4 bytes de espacio que probablemente no se asignarán nuevamente: tendrá un agujero .
Creo que es una locura: no puedes tener un programa que no sea un juguete en el que asignas memoria en el montón sin liberarla. Pero no tengo el conocimiento para explicar exactamente por qué es tan importante que para cada uno malloc()
debe haber un free()
.
Entonces, ¿existen circunstancias en las que podría ser apropiado usar un malloc()
sin usar free()
? Y si no, ¿cómo puedo explicárselo a mis profesores?
fuente
Respuestas:
Fácil: solo lea la fuente de prácticamente cualquier
malloc()/free()
implementación a medias seria . Con esto, me refiero al administrador de memoria real que maneja el trabajo de las llamadas. Esto podría estar en la biblioteca en tiempo de ejecución, la máquina virtual o el sistema operativo. Por supuesto, el código no es igualmente accesible en todos los casos.Es muy común asegurarse de que la memoria no se fragmente uniendo orificios adyacentes en orificios más grandes. Los asignadores más serios utilizan técnicas más serias para garantizar esto.
Entonces, supongamos que realiza tres asignaciones y desasignaciones y obtiene bloques dispuestos en la memoria en este orden:
+-+-+-+ |A|B|C| +-+-+-+
Los tamaños de las asignaciones individuales no importan. luego liberas el primero y el último, A y C:
+-+-+-+ | |B| | +-+-+-+
cuando finalmente liberas a B, (inicialmente, al menos en teoría) terminas con:
+-+-+-+ | | | | +-+-+-+
que se puede fragmentar en solo
+-+-+-+ | | +-+-+-+
es decir, un solo bloque libre más grande, no quedan fragmentos.
Referencias, según lo solicitado:
heap4.c
código en FreeRTOS .fuente
Las otras respuestas ya explican perfectamente bien que las implementaciones reales
malloc()
y defree()
hecho fusionan (desfragmentan) agujeros en trozos libres más grandes. Pero incluso si ese no fuera el caso, aún sería una mala idea renunciarfree()
.La cuestión es que su programa acaba de asignar (y quiere liberar) esos 4 bytes de memoria. Si va a funcionar durante un período de tiempo prolongado, es muy probable que necesite asignar nuevamente solo 4 bytes de memoria. Entonces, incluso si esos 4 bytes nunca se fusionarán en un espacio contiguo más grande, aún pueden ser reutilizados por el propio programa.
fuente
free
es que si se llama suficientes veces para tener un impacto en el rendimiento, es probable que también se llame suficientes veces, de modo que omitirlo hará un gran impacto en la memoria disponible. Es difícil imaginar una situación en un sistema embebido donde el rendimiento sufre continuamente debido a,free
pero dondemalloc
solo se llama un número finito de veces; Es un caso de uso bastante raro tener un dispositivo integrado que procesa datos de una sola vez y luego se reinicia.Es una tontería total, por ejemplo, hay muchas implementaciones diferentes
malloc
, algunas intentan hacer que el montón sea más eficiente como la de Doug Lea o esta .fuente
¿Están tus profesores trabajando con POSIX, por casualidad? Si están acostumbrados a escribir muchas aplicaciones de shell pequeñas y minimalistas, ese es un escenario en el que puedo imaginar que este enfoque no sería tan malo: liberar todo el montón de una vez en el ocio del sistema operativo es más rápido que liberar un mil variables. Si espera que su aplicación se ejecute durante uno o dos segundos, podría salirse con la suya sin ninguna desasignación.
Por supuesto, sigue siendo una mala práctica (las mejoras en el rendimiento siempre deben basarse en la elaboración de perfiles, no en una intuición vaga), y no es algo que deba decir a los estudiantes sin explicar las otras limitaciones, pero puedo imaginar un montón de pequeñas conchas. -aplicaciones para ser escritas de esta manera (si no se usa la asignación estática directamente) Si está trabajando en algo que se beneficia de no liberar sus variables, o está trabajando en condiciones de latencia extremadamente baja (en cuyo caso, ¿cómo puede permitirse la asignación dinámica y C ++?: D), o está haciendo algo muy, muy mal (como asignar una matriz de enteros asignando mil enteros uno tras otro en lugar de un solo bloque de memoria).
fuente
Mencionaste que eran profesores de electrónica. Pueden estar acostumbrados a escribir firmware / software en tiempo real, ya que a menudo se necesitan con precisión el tiempo de ejecución de algunas cosas. En esos casos, saber que tiene suficiente memoria para todas las asignaciones y no liberar y reasignar memoria puede proporcionar un límite de tiempo de ejecución más fácil de calcular.
En algunos esquemas, la protección de la memoria de hardware también puede usarse para asegurarse de que la rutina se complete en su memoria asignada o genere una trampa en lo que deberían ser casos muy excepcionales.
fuente
malloc
en absoluto en ese caso, sino que confiaran en la asignación estática (o quizás asignen una gran parte y luego manejen manualmente la memoria).Tomando esto desde un ángulo diferente al de los comentaristas y respuestas anteriores, una posibilidad es que sus profesores hayan tenido experiencia con sistemas donde la memoria se asignó de forma estática (es decir, cuando se compiló el programa).
La asignación estática viene cuando haces cosas como:
define MAX_SIZE 32 int array[MAX_SIZE];
En muchos sistemas embebidos y en tiempo real (los que tienen más probabilidades de ser encontrados por EE o CE), generalmente es preferible evitar la asignación de memoria dinámica por completo. Por lo tanto, los usos de
malloc
,new
y sus contrapartes de eliminación son raros. Además de eso, la memoria en las computadoras se ha disparado en los últimos años.Si tiene 512 MB disponibles y asigna estáticamente 1 MB, tiene aproximadamente 511 MB para recorrer antes de que explote el software (bueno, no exactamente ... pero ven conmigo aquí). Suponiendo que tiene 511 MB para abusar, si mallociza 4 bytes por segundo sin liberarlos, podrá ejecutar durante casi 73 horas antes de quedarse sin memoria. Teniendo en cuenta que muchas máquinas se apagan una vez al día, ¡eso significa que su programa nunca se quedará sin memoria!
En el ejemplo anterior, la fuga es de 4 bytes por segundo o 240 bytes / min. Ahora imagina que bajas esa relación byte / min. Cuanto menor sea esa proporción, más tiempo podrá ejecutar su programa sin problemas. Si sus
malloc
mensajes de correo electrónico son poco frecuentes, esa es una posibilidad real.Demonios, si sabes que solo vas a hacer
malloc
algo una vez, y esomalloc
nunca volverá a ser golpeado, entonces es muy parecido a la asignación estática, aunque no necesitas saber el tamaño de lo que estás asignando. frente. Por ejemplo: digamos que tenemos 512 MB nuevamente. Necesitamosmalloc
32 matrices de enteros. Estos son números enteros típicos: 4 bytes cada uno. Sabemos que los tamaños de estos arreglos nunca superarán los 1024 enteros. No se producen otras asignaciones de memoria en nuestro programa. ¿Tenemos suficiente memoria? 32 * 1024 * 4 = 131,072. 128 KB, así que sí. Tenemos mucho espacio. Si sabemos que nunca asignaremos más memoria, podemosmalloc
esas matrices sin liberarlas. Sin embargo, esto también puede significar que debe reiniciar la máquina / dispositivo si su programa falla. Si inicia / detiene su programa 4.096 veces, asignará los 512 MB. Si tiene procesos zombie, es posible que la memoria nunca se libere, incluso después de un bloqueo.Ahórrese el dolor y la miseria, y consuma este mantra como La Verdad Única: siempre
malloc
debe estar asociado con a . siempre debe tener un .free
new
delete
fuente
sizeof(int)
igual 2 en lugar de 4.Creo que la afirmación expresada en la pregunta no tiene sentido si se toma literalmente desde el punto de vista del programador, pero tiene verdad (al menos algo) desde el punto de vista del sistema operativo.
malloc () eventualmente terminará llamando a mmap () o sbrk () que buscará una página del sistema operativo.
En cualquier programa no trivial, las posibilidades de que esta página sea devuelta al SO durante la vida de un proceso son muy pequeñas, incluso si libera () la mayor parte de la memoria asignada. Por lo tanto, la memoria libre () solo estará disponible para el mismo proceso la mayor parte del tiempo, pero no para otros.
fuente
Tus profesores no están equivocados, pero también lo están (al menos son engañosos o simplifican demasiado). La fragmentación de la memoria causa problemas para el rendimiento y el uso eficiente de la memoria, por lo que a veces es necesario considerarla y tomar medidas para evitarla. Un truco clásico es, si asigna muchas cosas que son del mismo tamaño, tomar un grupo de memoria al inicio que es un múltiplo de ese tamaño y administrar su uso completamente internamente, asegurando así que no se produzca fragmentación en el Nivel de sistema operativo (y los agujeros en su mapeador de memoria interna serán exactamente del tamaño correcto para el próximo objeto de ese tipo que se presente).
Hay bibliotecas completas de terceros que no hacen nada más que manejar ese tipo de cosas por usted y, a veces, es la diferencia entre un rendimiento aceptable y algo que se ejecuta demasiado lento.
malloc()
yfree()
tomar una cantidad considerable de tiempo para ejecutar, que comenzará a notar si los llama mucho.Por lo tanto, evitando simplemente usando ingenuamente
malloc()
yfree()
se puede evitar ambos problemas de fragmentación y de rendimiento - pero cuando te pones derecho a ella, siempre debe asegurarse de quefree()
todo lo quemalloc()
a menos que tenga una muy buena razón para no hacerlo. Incluso cuando se utiliza un grupo de memoria interna, una buena aplicaciónfree()
almacenará la memoria del grupo antes de salir. Sí, el sistema operativo lo limpiará, pero si el ciclo de vida de la aplicación se cambia más tarde, sería fácil olvidar que el grupo todavía está rondando ...Las aplicaciones de larga duración, por supuesto, deben ser absolutamente escrupulosas a la hora de limpiar o reciclar todo lo que han asignado, o terminarán por quedarse sin memoria.
fuente
Tus profesores están planteando un punto importante. Desafortunadamente, el uso del inglés es tal que no estoy absolutamente seguro de qué dijeron. Permítanme responder la pregunta en términos de programas que no son juguetes que tienen ciertas características de uso de memoria y con los que he trabajado personalmente.
Algunos programas se comportan bien. Asignan memoria en oleadas: muchas asignaciones pequeñas o medianas seguidas de muchas liberaciones, en ciclos repetidos. En estos programas, los asignadores de memoria típicos funcionan bastante bien. Se fusionan en bloques liberados y, al final de una onda, la mayor parte de la memoria libre está en grandes trozos contiguos. Estos programas son bastante raros.
La mayoría de los programas se comportan mal. Asignan y desasignan memoria de forma más o menos aleatoria, en una variedad de tamaños, desde muy pequeños hasta muy grandes, y retienen un alto uso de los bloques asignados. En estos programas, la capacidad para fusionar bloques es limitada y con el tiempo terminan con la memoria muy fragmentada y relativamente no contigua. Si el uso total de memoria excede aproximadamente 1,5 GB en un espacio de memoria de 32 bits, y hay asignaciones de (digamos) 10 MB o más, eventualmente una de las asignaciones grandes fallará. Estos programas son comunes.
Otros programas liberan poca o ninguna memoria, hasta que se detienen. Asignan memoria progresivamente mientras se ejecutan, liberando solo pequeñas cantidades y luego se detienen, momento en el que se libera toda la memoria. Un compilador es así. También lo es una máquina virtual. Por ejemplo, el tiempo de ejecución de .NET CLR, escrito en C ++, probablemente nunca libera memoria. ¿Por qué debería hacerlo?
Y esa es la respuesta final. En aquellos casos en los que el programa tiene un uso de memoria suficientemente pesado, administrar la memoria con malloc y free no es una respuesta suficiente al problema. A menos que tenga la suerte de estar lidiando con un programa que se comporta bien, deberá diseñar uno o más asignadores de memoria personalizados que preasignen grandes cantidades de memoria y luego realicen una subasignación según la estrategia que elija. No puede usar gratis en absoluto, excepto cuando el programa se detiene.
Sin saber exactamente lo que dijeron sus profesores, para programas de escala de producción verdaderamente probablemente saldría de su lado.
EDITAR
Intentaré responder algunas de las críticas. Obviamente, SO no es un buen lugar para publicaciones de este tipo. Para ser claro: tengo alrededor de 30 años de experiencia escribiendo este tipo de software, incluidos un par de compiladores. No tengo referencias académicas, solo mis propios moretones. No puedo evitar sentir que las críticas provienen de personas con una experiencia mucho más limitada y corta.
Repetiré mi mensaje clave: equilibrar malloc y free no es una solución suficiente para la asignación de memoria a gran escala en programas reales. Bloque de coalescencia es normal, y permite ganar tiempo, pero es que no lo suficiente. Necesita asignadores de memoria serios e inteligentes, que tienden a capturar la memoria en trozos (usando malloc o lo que sea) y rara vez se liberan. Este es probablemente el mensaje que los profesores de OP tenían en mente, que él entendió mal.
fuente
Me sorprende que nadie haya citado El libro todavía:
http://sarabander.github.io/sicp/html/5_002e3.xhtml#FOOT298
Entonces, de hecho, muchos programas pueden funcionar bien sin molestarse en liberar memoria.
fuente
Conozco un caso en el que liberar memoria explícitamente es peor que inútil . Es decir, cuando necesite todos sus datos hasta el final de la vida útil del proceso. En otras palabras, cuando liberarlos solo es posible justo antes de la finalización del programa. Dado que cualquier sistema operativo moderno se encarga de liberar memoria cuando un programa muere,
free()
no es necesario llamar en ese caso. De hecho, puede ralentizar la finalización del programa, ya que es posible que necesite acceder a varias páginas de la memoria.fuente