¿Alguna vez está bien * no * usar free () en la memoria asignada?


83

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?


11
No están "equivocados": tienen un punto válido (aunque limitado) sobre la fragmentación de regiones libres aisladas muy pequeñas, y probablemente lo expresaron con un poco más de cuidado de lo que usted lo ha informado.
Chris Stratton

2
Las únicas ocasiones en las que no necesita liberar memoria es cuando usa la memoria administrada o cuando va a reutilizar la memoria asignada original. Sospecho que la razón por la que dos instructores han dicho esto es porque estás hablando de un programa específico que puede reutilizar la memoria. En este caso, seguiría usando free () pero solo al final de su aplicación. ¿Estás seguro de que ese no era su significado?
krowe

17
@Marian: Un profesor afirmó que en C y C ++ es necesario liberar la memoria asignada en una función definida en el mismo archivo .c / .cxx en el que se asignó ... Estas personas a veces parecen sufrir gravemente de hipoxia por vivir demasiado alto en una torre de marfil.
PlasmaHH

4
Hay bastantes programas que no son de juguete que no desasignan la memoria, y dejar que el sistema operativo lo limpie todo al salir del proceso es mucho más rápido que (fastidiosamente) mantener un montón de libros para que pueda hacerlo usted mismo.
Donal Fellows

9
Nunca fusiones cosas que escuchaste en tu cerebro sin cuestionarlas. He tenido muchos profesores, lectores y correctores que estaban equivocados o desactualizados. Y siempre analiza lo que dicen con mucha precisión. Nuestra gente a menudo es bastante precisa y puede decir cosas que son correctas, pero es fácil de entender de manera incorrecta o con una prioridad incorrecta para alguien que se siente como en casa solo en el lenguaje común. Por ejemplo, recuerdo que en la escuela, un maestro dijo "¿Hiciste tu tarea?", Yo dije "No". Si bien estaba en lo cierto, el maestro encontró esto ofensivo, porque me ahorré tiempo para encontrar excusas poco convincentes, lo cual no esperaba.
Sebastian Mach

Respuestas:


100

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:

  • Intente leer el código de dlmalloc . Es mucho más avanzado, siendo una implementación de calidad de producción completa.
  • Incluso en aplicaciones integradas, hay disponibles implementaciones de desfragmentación. Vea, por ejemplo, estas notas sobre el heap4.ccódigo en FreeRTOS .

1
¿Puede proporcionarme algunas referencias?
Nick

4
Creo que valdría la pena mencionar que el espacio de direcciones virtuales no es una representación directa de la memoria física. Y esa fragmentación en la memoria física puede ser tratada por el sistema operativo, mientras que la memoria virtual no liberada por el proceso tampoco se liberará físicamente.
lapk

@PetrBudnik sería raro que la memoria virtual mapas 1-1 a la memoria física de todos modos, el sistema operativo va a pensar en las asignaciones de página y ser capaz de cambiar dentro y fuera con un mínimo de alboroto
monstruo de trinquete

3
Comentario muy quisquilloso: si bien el concepto está bien explicado, el ejemplo real fue elegido un poco ... desafortunado. Para cualquiera que mire el código fuente de dlmalloc y se confunda: los bloques por debajo de un tamaño específico son siempre potencias de 2 y se fusionan / dividen en consecuencia. Así que terminaríamos (posiblemente) con un bloque de 8 bytes y 1 bloque de 4 bytes, pero sin un bloque de 12 bytes. Ese es un enfoque bastante estándar para los asignadores, al menos en computadoras de escritorio, aunque quizás las aplicaciones integradas intenten ser más cuidadosas con su sobrecarga.
Voo

@Voo Eliminé la mención de un tamaño para los bloques en el ejemplo, no importaba de todos modos. ¿Mejor?
relajarse el

42

Las otras respuestas ya explican perfectamente bien que las implementaciones reales malloc()y de free()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 renunciar free().

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.


6
+1 Exactamente. El caso freees 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, freepero donde mallocsolo 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.
Jason C

10

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 .


9

¿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).


Dejar que el sistema operativo libere todo al final no se trata solo de rendimiento, sino que también se necesita mucha menos lógica para funcionar.
hugomg

@missingno En otras palabras, le permite salirse con la suya con lo difícil que puede ser la administración de la memoria :) Sin embargo, lo vería como un argumento en contra de los lenguajes no administrados: si su razón es complicada liberando lógica en lugar de rendimiento, podría ser mejor de usar un lenguaje / entorno que lo cuide por usted.
Luaan

5

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.


10
Ese es un buen punto. Sin embargo, esperaría que no lo usaran mallocen 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).
Luaan

2

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, newy 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 mallocmensajes de correo electrónico son poco frecuentes, esa es una posibilidad real.

Demonios, si sabes que solo vas a hacer mallocalgo una vez, y eso mallocnunca 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. Necesitamos malloc32 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, podemosmallocesas 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: siempremalloc debe estar asociado con a . siempre debe tener un . freenewdelete


2
En la mayoría de los sistemas integrados y en tiempo real, una bomba de tiempo que provoque fallas después de solo 73 horas sería un problema grave.
Ben Voigt

enteros típicos ??? El número entero es de al menos 16 bits y en microchips pequeños suele ser de 16 bits. Generalmente hay más dispositivos con sizeof(int)igual 2 en lugar de 4.
ST3

2

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.


2

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()y free()tomar una cantidad considerable de tiempo para ejecutar, que comenzará a notar si los llama mucho.

Por lo tanto, evitando simplemente usando ingenuamente malloc()y free()se puede evitar ambos problemas de fragmentación y de rendimiento - pero cuando te pones derecho a ella, siempre debe asegurarse de que free()todo lo que malloc()a menos que tenga una muy buena razón para no hacerlo. Incluso cuando se utiliza un grupo de memoria interna, una buena aplicación free()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.


1

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.


1
Los programas que se comportan mal muestran que los asignadores decentes deberían usar grupos separados para fragmentos de diferentes tamaños. Con la memoria virtual, tener muchos grupos diferentes separados no debería ser un problema importante. Si cada fragmento se redondea a una potencia de dos, y cada grupo contiene fragmentos redondeados de un solo tamaño, no puedo ver cómo la fragmentación podría volverse muy mala. En el peor de los casos, un programa podría dejar de estar interesado repentinamente en un cierto rango de tamaño, dejando algunas piscinas en gran parte vacías sin utilizar; Sin embargo, no creo que este sea un comportamiento muy común.
Marc van Leeuwen

5
Cita muy necesaria para la afirmación de que los programas escritos de manera competente no liberan memoria hasta que se cierra la aplicación.

12
Toda esta respuesta se lee como una serie de conjeturas y suposiciones al azar. ¿Hay algo que respalde todo esto?
Chris Hayes

4
No estoy seguro de lo que quiere decir cuando dice que el tiempo de ejecución de .NET CLR no libera memoria. Por lo que he probado, lo hace, si puede.
Theodoros Chatzigiannakis

1
@vonbrand: GCC tiene varios asignadores, incluida su propia marca de recolector de basura. Consume memoria durante los pases y la recopila entre pases. La mayoría de los compiladores para otros lenguajes tienen 2 pasadas como máximo y poca o ninguna memoria libre entre pasadas. Si no está de acuerdo, me complace investigar cualquier ejemplo que dé.
david.pfx

1

Me sorprende que nadie haya citado El libro todavía:

Esto puede no ser cierto eventualmente, porque las memorias pueden ser lo suficientemente grandes como para que sea imposible quedarse sin memoria libre durante la vida útil de la computadora. Por ejemplo, hay alrededor de 3 x 10 13 microsegundos en un año, por lo que si tuviéramos que hacer estafa una vez por microsegundo, necesitaríamos alrededor de 10 15 celdas de memoria para construir una máquina que pudiera funcionar durante 30 años sin quedarse sin memoria. Esa cantidad de memoria parece absurdamente grande para los estándares actuales, pero no es físicamente imposible. Por otro lado, los procesadores se están volviendo más rápidos y una computadora futura puede tener una gran cantidad de procesadores operando en paralelo en una sola memoria, por lo que es posible usar la memoria mucho más rápido de lo que hemos postulado.

http://sarabander.github.io/sicp/html/5_002e3.xhtml#FOOT298

Entonces, de hecho, muchos programas pueden funcionar bien sin molestarse en liberar memoria.


1

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.