memoria c++ c memory-management memory-leaks heap

c++ - memoria heap



¿Está bien** no*usar free() en la memoria asignada? (11)

Estoy estudiando ingeniería informática y tengo algunos cursos de electrónica. Escuché de dos de mis profesores (de estos cursos) que es posible evitar el uso de la función free() (después de malloc() , calloc() , etc.) porque los espacios de memoria asignados probablemente no se volverán a utilizar para asignar otra memoria. Es decir, por ejemplo, si asigna 4 bytes y los libera, tendrá 4 bytes de espacio que probablemente no se asignarán de nuevo: tendrá un agujero .

Creo que es una locura: no se puede tener un programa que no sea de juguete, donde se asigna memoria en el montón sin liberarlo. Pero no tengo el conocimiento para explicar exactamente por qué es tan importante que para cada malloc() debe haber un free() .

Entonces: ¿hay alguna vez circunstancias en las que pueda ser apropiado usar un malloc() sin usar free() ? Y si no, ¿cómo puedo explicar esto a mis profesores?


¿Tus profesores trabajan 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 imaginarme que este enfoque no sería demasiado 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, puede salirse con la suya sin ninguna desasignación.

Por supuesto, sigue siendo una mala práctica (las mejoras de rendimiento siempre deben basarse en el perfil, no en la sensación visceral), y no es algo que se debe decir a los estudiantes sin explicar las otras limitaciones, pero puedo imaginar muchas pequeñas conchas -aplicaciones que deben escribirse de esta manera (si no se utiliza la asignación estática directamente). Si está trabajando en algo que se beneficia al no liberar sus variables, está trabajando en condiciones de extrema baja latencia (en cuyo caso, ¿cómo puede siquiera pagar 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).


Conozco un caso cuando liberar memoria explícitamente es peor que inútil . Es decir, cuando necesita 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. Como cualquier SO moderno se ocupa de liberar memoria cuando un programa muere, no es necesario llamar a free() en ese caso. De hecho, puede ralentizar la finalización del programa, ya que puede necesitar acceder a varias páginas en la memoria.


Creo que la afirmación declarada en la pregunta es absurda si se toma literalmente desde el punto de vista del programador, pero tiene verdad (al menos algo) desde la perspectiva del sistema operativo.

malloc () eventualmente terminará llamando a mmap () o sbrk () que capturará una página del sistema operativo.

En cualquier programa no trivial, las posibilidades de que esta página se devuelva alguna vez al sistema operativo durante la vida útil de un proceso son muy pequeñas, incluso si libera () la mayor parte de la memoria asignada. Así que la memoria gratuita () ''d solo estará disponible para el mismo proceso la mayor parte del tiempo, pero no para otros.


Es una tontería total, por ejemplo, hay muchas implementaciones diferentes de malloc , algunas intentan hacer que el montón sea más eficiente como el de Doug Lea o this .


Fácil: solo lea la fuente de casi cualquier implementación malloc()/free() medias. Con esto, me refiero al administrador de memoria real que maneja el trabajo de las llamadas. Esto podría estar en la biblioteca de 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.

Asegurarse de que la memoria no está fragmentada, uniendo agujeros adyacentes en agujeros más grandes, es muy común. Los asignadores más serios usan técnicas más serias para asegurar esto.

Entonces, supongamos que haces tres asignaciones y desasignaciones y obtienes bloques distribuidos 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 B, tú (inicialmente, al menos en teoría) terminas con:

+-+-+-+ | | | | +-+-+-+

que puede ser des-fragmentado en solo

+-+-+-+ | | +-+-+-+

es decir, un único bloque libre más grande, no quedan fragmentos.

Referencias, según lo solicitado:

  • Intenta leer el código de dlmalloc . Estoy mucho más avanzado, siendo una implementación de calidad de producción completa.
  • Incluso en aplicaciones integradas, las implementaciones de des-fragmentación están disponibles. Ver por ejemplo estas notas en el código heap4.c en FreeRTOS .

Las otras respuestas ya explican perfectamente que las implementaciones reales de malloc() y free() efectivamente fusionan (desfragmentan) agujeros en segmentos libres más grandes. Pero incluso si ese no fuera el caso, todavía sería una mala idea renunciar a free() .

El problema 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 bastante probable que necesite asignar solo 4 bytes de memoria nuevamente. Entonces, incluso si esos 4 bytes nunca se fusionarán en un espacio contiguo más grande, aún pueden ser reutilizados por el programa mismo.


Me sorprende que nadie haya citado The Book todavía:

Esto puede no ser cierto eventualmente, porque los recuerdos 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 ⋅ 10 13 microsegundos en un año, por lo que si tuviéramos que contras una vez por microsegundo necesitaríamos unas 10 15 células de memoria para construir una máquina que pudiera funcionar durante 30 años sin quedarse sin memoria. Esa gran cantidad de memoria parece absurdamente grande según los estándares actuales, pero no es físicamente imposible. Por otro lado, los procesadores son cada vez más rápidos y una computadora futura puede tener un gran número de procesadores operando en paralelo en una sola memoria, por lo que es posible que se agote la memoria mucho más rápido de lo que hemos postulado.

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

Así que, de hecho, muchos programas pueden funcionar perfectamente sin molestarse en liberar ningún tipo de memoria.


Mencionaste que eran profesores de electrónica. Se pueden usar para escribir software de firmware / en tiempo real, y si se puede cronometrar con precisión la ejecución de algo, a menudo se necesita. En esos casos, saber que tienes suficiente memoria para todas las asignaciones y no liberar y reasignar la memoria puede dar un límite más fácil de calcular en el tiempo de ejecución.

En algunos esquemas, la protección de la memoria del hardware también se puede usar para asegurar que la rutina se complete en su memoria asignada o genere una trampa en casos que deberían ser muy excepcionales.


Sus profesores no están equivocados, pero también lo están (son al menos engañosos o simplifican demasiado). La fragmentación de la memoria causa problemas de rendimiento y uso eficiente de la memoria, por lo que a veces hay que 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 haya fragmentación en el momento. Nivel de SO (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 aparezca).

Hay bibliotecas enteras de terceros que no hacen nada más que manejar ese tipo de cosas, y a veces es la diferencia entre un rendimiento aceptable y algo que se ejecuta demasiado lento. malloc() y free() tardan una cantidad notable de tiempo en ejecutarse, lo que comenzarás a notar si los llamas mucho.

Así que evitando ingenuamente el uso de malloc() y free() puede evitar tanto la fragmentación como los problemas de rendimiento, pero cuando llegue a eso, siempre debe asegurarse de free() todo lo que malloc() menos que tenga una muy buena razón para hacer lo contrario. Incluso cuando se usa un grupo de memoria interna, una buena aplicación free() la memoria del grupo antes de que salga. 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á dando vueltas ...

Por supuesto, las aplicaciones de larga duración deben ser totalmente escrupulosas para limpiar o reciclar todo lo que han asignado, o terminan quedándose sin memoria.


Tomando esto desde un ángulo diferente al de los comentaristas y respuestas anteriores, una posibilidad es que los profesores hayan tenido experiencia con sistemas en los que la memoria se asignó estáticamente (es decir, cuando el programa se compiló).

La asignación estática se produce cuando haces cosas como:

define MAX_SIZE 32 int array[MAX_SIZE];

En muchos sistemas incorporados y en tiempo real (los que con mayor probabilidad encontrarán los EE o los CE), generalmente es preferible evitar por completo la asignación de memoria dinámica. Por lo tanto, los usos de malloc , new y sus equivalentes 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 pasar sin problemas antes de que explote su software (bueno, no exactamente ... pero acompáñeme aquí). Suponiendo que tiene 511 MB de abuso, si malloc 4 bytes por segundo sin liberarlos, podrá ejecutar durante casi 73 horas antes de que se quede 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 imagine que baja esa relación byte / min. Cuanto menor es esa relación, más tiempo puede ejecutar su programa sin problemas. Si tus malloc son infrecuentes, esa es una posibilidad real.

Diablos, si sabes que solo vas a malloc algo una vez, y que malloc 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 en la delantera. Por ejemplo: supongamos que tenemos 512 MB de nuevo. Necesitamos malloc 32 matrices de enteros. Estos son enteros típicos: 4 bytes cada uno. Sabemos que los tamaños de estas matrices nunca superarán los 1024 enteros. No hay 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, podemos malloc forma segura 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 tienes procesos zombies es posible que la memoria nunca se libere, incluso después de un bloqueo.

Ahórrese dolor y miseria, y consuma este mantra como La Verdad Única: malloc siempre debe asociarse con un free . new siempre debe tener una delete .


Tus profesores están planteando un punto importante. Lamentablemente, el uso de inglés es tal que no estoy absolutamente seguro de qué es lo que dijeron. Permítanme responder la pregunta en términos de programas que no son de juguete que tienen ciertas características de uso de memoria y con los que he trabajado personalmente.

Algunos programas se comportan bien. Asignan memoria en ondas: 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 bloques liberados y al final de una ola la mayor parte de la memoria libre se encuentra 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 conservan un alto uso de bloques asignados. En estos programas, la capacidad de unir bloques es limitada y con el tiempo terminan con una 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. Gradualmente asignan memoria 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í. Entonces es una VM. Por ejemplo, el tiempo de ejecución .NET CLR, escrito en C ++, probablemente nunca libera ninguna memoria. ¿Por qué debería?

Y esa es la respuesta final. En aquellos casos en los que el programa es suficientemente pesado en el uso de la memoria, administrar la memoria utilizando malloc y libre no es una respuesta suficiente al problema. A menos que tenga la suerte de estar lidiando con un programa de buen comportamiento, deberá diseñar uno o más asignadores de memoria personalizados que preasignan grandes porciones de memoria y luego subasignar de acuerdo con la estrategia que elija. No puede usarlo gratis, excepto cuando el programa se detiene.

Sin saber exactamente lo que dijeron sus profesores, para programas verdaderamente a escala de producción, probablemente me pondría de su parte.

EDITAR

Tendré una oportunidad para responder a algunas de las críticas. Obviamente SO no es un buen lugar para publicaciones de este tipo. Para ser claros: tengo alrededor de 30 años de experiencia escribiendo este tipo de software, que incluye 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 estrecha y corta.

Repetiré mi mensaje clave: equilibrar malloc y liberar no es una solución suficiente para la asignación de memoria a gran escala en programas reales. El bloqueo de bloques es normal y compra tiempo, pero no es suficiente. Necesitas asignadores de memoria serios e inteligentes, que tienden a tomar la memoria en pedazos (usando malloc o lo que sea) y raramente gratis. Este es probablemente el mensaje que los profesores de OP tenían en mente, lo cual malinterpretó.