c++ - ¿Las fugas de memoria están bien?




memory-leaks (20)

¿Es aceptable tener una pérdida de memoria en su aplicación C o C ++?

¿Qué sucede si asigna algo de memoria y la usa hasta la última línea de código en su aplicación (por ejemplo, el destructor de un objeto global)? Siempre que el consumo de memoria no crezca con el tiempo, ¿está bien confiar en que el sistema operativo libere su memoria cuando finalice su aplicación (en Windows, Mac y Linux)? ¿Consideraría esto como una pérdida de memoria real si la memoria se usara continuamente hasta que el sistema operativo la liberara?

¿Qué pasaría si una biblioteca de terceros te forzara esta situación? ¿Se negaría a usar esa biblioteca de terceros sin importar cuán grande pueda ser?

Solo veo una desventaja práctica, y es que estas fugas benignas se mostrarán con herramientas de detección de fugas de memoria como falsos positivos.


Creo que en tu situación la respuesta puede ser que está bien. Pero definitivamente necesitas documentar que la pérdida de memoria es una decisión consciente. No quiere que venga un programador de mantenimiento, abofetee su código dentro de una función y llámelo un millón de veces. Entonces, si toma la decisión de que una fuga está bien, debe documentarla (EN GRANDES CARTAS) para quien tenga que trabajar en el programa en el futuro.

Si se trata de una biblioteca de terceros, puede quedar atrapado. Pero definitivamente documente que se produce esta fuga.

Pero básicamente, si la pérdida de memoria es una cantidad conocida, como un búfer de 512 KB o algo así, entonces no es un problema. Si la pérdida de memoria sigue creciendo como cada vez que llama a una biblioteca, su memoria aumenta en 512 KB y no se libera, entonces puede tener un problema. Si lo documenta y controla la cantidad de veces que se ejecuta la llamada, puede ser manejable. Pero entonces realmente necesita documentación porque mientras 512 no es mucho, 512 más de un millón de llamadas es mucho.

También necesitas revisar la documentación de tu sistema operativo. Si se trata de un dispositivo integrado, puede haber sistemas operativos que no liberen toda la memoria de un programa que sale. No estoy seguro, tal vez esto no es cierto. Pero vale la pena mirar.


Creo que la respuesta es no, nunca permitir una pérdida de memoria, y tengo algunas razones que no he visto explícitamente. Aquí hay excelentes respuestas técnicas, pero creo que la respuesta real depende de más razones sociales / humanas.

(En primer lugar, tenga en cuenta que, como han mencionado otros, una verdadera fuga es cuando su programa, en cualquier momento, pierde el seguimiento de los recursos de memoria que ha asignado. En C, esto sucede cuando se malloc() a un puntero y se deja que el puntero salga del alcance sin hacer un free() primero.)

El quid importante de su decisión aquí es el hábito. Cuando codificas en un lenguaje que usa punteros, vas a usar mucho los punteros. Y los punteros son peligrosos; Son la forma más fácil de agregar todo tipo de problemas graves a su código.

Cuando estás programando, a veces vas a estar concentrado y otras veces estás cansado o enojado o preocupado. Durante esos momentos un tanto distraídos, estás codificando más en el piloto automático. El efecto del piloto automático no distingue entre un código único y un módulo en un proyecto más grande. Durante esos momentos, los hábitos que establezca serán los que terminarán en su base de código.

Así que no, nunca permita fugas de memoria por la misma razón por la que aún debe revisar sus puntos ciegos al cambiar de carril, incluso si es el único automóvil en la carretera en este momento. Durante los momentos en que su cerebro activo está distraído, los buenos hábitos son todo lo que puede salvarlo de errores desastrosos.

Más allá del problema del "hábito", los indicadores son complejos y, a menudo, requieren una gran cantidad de capacidad mental para realizar un seguimiento mental. Es mejor no "enturbiar el agua" cuando se trata de usar punteros, especialmente cuando eres nuevo en la programación.

También hay un aspecto más social. Con el uso adecuado de malloc() y free() , cualquier persona que mire su código estará a gusto; usted está administrando sus recursos. Sin embargo, si no lo hace, inmediatamente sospecharán un problema.

Tal vez haya averiguado que la pérdida de memoria no daña nada en este contexto, pero cada mantenedor de su código tendrá que resolverlo también en su cabeza cuando lea ese fragmento de código. Al utilizar free() , elimina la necesidad de considerar el problema.

Finalmente, la programación es escribir un modelo mental de un proceso a un lenguaje no ambiguo para que una persona y una computadora puedan entender perfectamente dicho proceso. Una parte vital de la buena práctica de programación nunca es introducir una ambigüedad innecesaria.

La programación inteligente es flexible y genérica. La mala programación es ambigua.


En este tipo de pregunta, el contexto lo es todo. Personalmente, no soporto las fugas, y en mi código hago todo lo posible para solucionarlas si surgen, pero no siempre vale la pena reparar una fuga, y cuando la gente me paga por la hora que tengo, en ocasiones les dije que no valía la pena pagar una fuga en su código. Dejame darte un ejemplo:

Estaba evaluando un proyecto, haciendo un trabajo de perfección y arreglando muchos errores. Hubo una fuga durante la inicialización de las aplicaciones que rastreé y entendí completamente. Repararlo correctamente habría requerido un día más o menos refactorizar una parte del código funcional. Podría haber hecho algo intrépido (como rellenar el valor en un global y captarlo en algún punto que sé que ya no se usaba para liberarlo), pero eso solo habría causado más confusión en el siguiente tipo que tuvo que tocar el código.

Personalmente, no habría escrito el código de esa manera en primer lugar, pero la mayoría de nosotros no siempre trabajamos en bases de código prístinas y bien diseñadas, y algunas veces hay que mirar estas cosas de manera pragmática. La cantidad de tiempo que me hubiera costado arreglar esa fuga de 150 bytes se podría dedicar a realizar mejoras algorítmicas que recortaron megabytes de RAM.

En última instancia, decidí que no valía la pena repararlo con una fuga de 150 bytes para una aplicación que se usaba en torno a un concierto de ram y que funcionaba en una máquina dedicada, por lo que escribí un comentario que decía que se había filtrado, qué había que cambiar para corregirlo. Eso, y por qué no valía la pena en ese momento.


En teoría no, en la práctica depende .

Realmente depende de cuántos datos está trabajando el programa, con qué frecuencia se ejecuta el programa y si se está ejecutando o no constantemente.

Si tengo un programa rápido que lee una pequeña cantidad de datos hace un cálculo y sale, nunca se notará una pequeña pérdida de memoria. Debido a que el programa no se ejecuta durante mucho tiempo y solo utiliza una pequeña cantidad de memoria, la fuga será pequeña y se liberará cuando exista el programa.

Por otro lado, si tengo un programa que procesa millones de registros y se ejecuta durante mucho tiempo, una pequeña pérdida de memoria puede hacer que la máquina pierda el tiempo suficiente.

En cuanto a las bibliotecas de terceros que tienen fugas, si causan un problema, corrija la biblioteca o encuentre una mejor alternativa. Si no causa un problema, ¿realmente importa?


Incluso si está seguro de que su pérdida de memoria 'conocida' no causará estragos, no lo haga. En el mejor de los casos, allanará el camino para que cometa un error similar y probablemente más crítico en un momento y lugar diferente.

Para mí, preguntar esto es como preguntar "¿Puedo romper la luz roja a las 3 AM de la mañana cuando no hay nadie cerca?". Bien seguro, puede que no cause ningún problema en ese momento, pero proporcionará una palanca para que hagas lo mismo en hora punta.


Me sorprende ver tantas definiciones incorrectas de lo que realmente es una pérdida de memoria. Sin una definición concreta, una discusión sobre si es algo malo o no irá a ninguna parte.

Como algunos comentaristas han señalado correctamente, una pérdida de memoria solo ocurre cuando la memoria asignada por un proceso queda fuera del alcance en la medida en que el proceso ya no puede hacer referencia o eliminarla.

Un proceso que agarra más y más memoria no necesariamente tiene fugas. Mientras sea capaz de hacer referencia y desasignar esa memoria, permanecerá bajo el control explícito del proceso y no se habrá filtrado. El proceso puede estar mal diseñado, especialmente en el contexto de un sistema donde la memoria es limitada, pero esto no es lo mismo que una fuga. A la inversa, perder el alcance de, digamos, un búfer de 32 bytes sigue siendo una fuga, aunque la cantidad de memoria perdida es pequeña. Si cree que esto es insignificante, espere hasta que alguien ajuste un algoritmo alrededor de su biblioteca y lo llame 10,000 veces.

No veo ninguna razón para permitir fugas en su propio código, por pequeño que sea. Los lenguajes de programación modernos, como C y C ++, hacen todo lo posible para ayudar a los programadores a prevenir tales fugas y rara vez existe un buen argumento para no adoptar buenas técnicas de programación, especialmente cuando se combinan con instalaciones de lenguaje específicas, para evitar fugas.

En lo que respecta al código existente o de terceros, donde su control sobre la calidad o la capacidad para realizar un cambio puede ser muy limitado, dependiendo de la gravedad de la fuga, puede verse obligado a aceptar o tomar medidas de mitigación, como reiniciar el proceso regularmente para reducir El efecto de la fuga.

Es posible que no sea posible cambiar o reemplazar el código existente (con fugas) y, por lo tanto, es posible que esté obligado a aceptarlo. Sin embargo, esto no es lo mismo que declarar que está bien.


No considero que sea una pérdida de memoria a menos que la cantidad de memoria que se está "utilizando" siga creciendo. Tener un poco de memoria inédita, aunque no es ideal, no es un gran problema a menos que la cantidad de memoria requerida siga creciendo.


No hay nada conceptualmente incorrecto en tener que limpiar el sistema operativo después de ejecutar la aplicación.

Realmente depende de la aplicación y de cómo se ejecutará. Las fugas que ocurren continuamente en una aplicación que debe ejecutarse durante semanas deben ser atendidas, pero una pequeña herramienta que calcula un resultado sin una necesidad de memoria demasiado alta no debería ser un problema.

Existe una razón por la que muchos lenguajes de scripting no recolectan las referencias cíclicas ... para sus patrones de uso, no es un problema real y, por lo tanto, sería un desperdicio de recursos tan grande como la memoria desperdiciada.


Primero debe darse cuenta de que hay una gran diferencia entre una pérdida de memoria percibida y una pérdida de memoria real. Con mucha frecuencia, las herramientas de análisis informarán sobre muchas pistas falsas y etiquetarán algo como que se ha filtrado (memoria o recursos como identificadores, etc.) donde en realidad no lo está. Muchas veces esto se debe a la arquitectura de la herramienta de análisis. Por ejemplo, ciertas herramientas de análisis informarán los objetos de tiempo de ejecución como pérdidas de memoria porque nunca ve los objetos liberados. Pero la desasignación se produce en el código de cierre del tiempo de ejecución, que la herramienta de análisis podría no ver.

Dicho esto, todavía habrá ocasiones en las que tendrá pérdidas de memoria reales que son muy difíciles de encontrar o muy difíciles de solucionar. Así que ahora la pregunta es: ¿está bien dejarlos en el código?

La respuesta ideal es, "no, nunca". Una respuesta más pragmática puede ser "no, casi nunca". Muy a menudo en la vida real tiene un número limitado de recursos y tiempo para resolver y una lista interminable de tareas. Cuando una de las tareas es eliminar las pérdidas de memoria, la ley de los rendimientos decrecientes muy a menudo entra en juego. Podría eliminar, por ejemplo, el 98% de todas las fugas de memoria en una aplicación en una semana, pero el 2% restante podría llevar meses. En algunos casos, incluso podría ser imposible eliminar ciertas fugas debido a la arquitectura de la aplicación sin una refactorización importante del código. Tienes que sopesar los costos y beneficios de eliminar el 2% restante.


Puedo contar con una mano el número de fugas "benignas" que he visto a lo largo del tiempo.

Así que la respuesta es un sí muy calificado.

Un ejemplo. Si tiene un recurso singleton que necesita un búfer para almacenar una cola circular o deque, pero no sabe qué tan grande debe ser el búfer y no puede pagar la sobrecarga de bloqueo o de cada lector, luego asigne un búfer de duplicación exponencial, pero no liberar los antiguos perderá una cantidad limitada de memoria por cola / deque. El beneficio para estos es que aceleran cada acceso dramáticamente y pueden cambiar la asintótica de las soluciones multiprocesador al no arriesgarse a disputar un bloqueo.

He visto que este enfoque es muy beneficioso para las cosas con recuentos muy claros, como los deques de robo de trabajo por CPU, y en mucho menor grado en el búfer utilizado para mantener el estado singleton /proc/self/maps en Hans El recolector de basura conservador de Boehm para C / C ++, que se utiliza para detectar los conjuntos de raíces, etc.

Si bien técnicamente es una fuga, ambos casos están limitados en tamaño y en el caso de deque de robo de trabajo circular que se puede cultivar, hay una gran ganancia de rendimiento a cambio de un factor acotado de 2 en el uso de memoria para las colas.


Si asigna un montón de almacenamiento dinámico al principio de su programa, y ​​no lo libera al salir, eso no es una pérdida de memoria per se. Una pérdida de memoria es cuando su programa recorre una sección del código, y ese código asigna un montón y luego "pierde el rastro" de él sin liberarlo.

De hecho, no es necesario realizar llamadas a free () o eliminar justo antes de salir. Cuando el proceso finaliza, toda su memoria es reclamada por el sistema operativo (este es ciertamente el caso con POSIX. En otros sistemas operativos, especialmente los integrados, YMMV).

La única precaución que tengo al no liberar la memoria en el momento de la salida es que si alguna vez refactoriza su programa para que, por ejemplo, se convierta en un servicio que espere la entrada, haga lo que sea que haga su programa, luego haga un bucle para esperar Otra llamada de servicio, entonces lo que ha codificado puede convertirse en una pérdida de memoria.


Si bien la mayoría de las respuestas se concentran en las pérdidas de memoria reales (que no están bien nunca, porque son un signo de codificación descuidada), esta parte de la pregunta me parece más interesante:

¿Qué sucede si asigna algo de memoria y la usa hasta la última línea de código en su aplicación (por ejemplo, el deconstructor de un objeto global)? Siempre que el consumo de memoria no crezca con el tiempo, ¿está bien confiar en que el sistema operativo libere su memoria cuando finalice su aplicación (en Windows, Mac y Linux)? ¿Consideraría esto como una pérdida de memoria real si la memoria se usara continuamente hasta que el sistema operativo la liberara?

Si se utiliza la memoria asociada, no puede liberarla antes de que finalice el programa. Si el libre es hecho por la salida del programa o por el sistema operativo no importa. Mientras esto esté documentado, para que el cambio no introduzca pérdidas de memoria reales, y siempre que no haya un destructor de C ++ o una función de limpieza de C involucrada en la imagen. Un archivo no cerrado puede revelarse a través de un objeto FILE filtrado, pero un fclose () faltante también puede hacer que el búfer no se vacíe.

Así que, volviendo al caso original, en mi humilde opinión está perfectamente bien en sí mismo, tanto que Valgrind, uno de los detectores de fugas más potentes, tratará dichas fugas solo si se solicita. En Valgrind, cuando sobrescribes un puntero sin liberarlo de antemano, se considera una pérdida de memoria, ya que es más probable que vuelva a ocurrir y que el montón aumente infinitamente.

Entonces, no hay nfreed bloques de memoria que todavía son accesibles. Uno podría asegurarse de liberarlos a todos a la salida, pero eso es solo una pérdida de tiempo en sí mismo. El punto es si podrían ser liberados antes . Reducir el consumo de memoria es útil en cualquier caso.


Como regla general, si tiene pérdidas de memoria que cree que no puede evitar, entonces debe pensar más en la propiedad de los objetos.

Pero a su pregunta, mi respuesta en pocas palabras es En código de producción, sí. Durante el desarrollo, no . Esto puede parecer al revés, pero aquí está mi razonamiento:

En la situación que describe, donde se guarda la memoria hasta el final del programa, es perfectamente correcto no liberarla. Una vez que el proceso finalice, el sistema operativo se limpiará de todos modos. De hecho, podría mejorar la experiencia del usuario: en un juego en el que he trabajado, los programadores pensaron que sería más limpio liberar toda la memoria antes de salir, ¡lo que provocó que el cierre del programa demorara hasta medio minuto! Un cambio rápido que acaba de llamar exit () hizo que el proceso desapareciera de inmediato y devolvió al usuario al escritorio donde quería estar.

Sin embargo, tiene razón con respecto a las herramientas de depuración: darán un ataque, y todos los falsos positivos podrían hacer que encontrar su verdadera memoria sea un dolor. Y debido a eso, siempre escriba el código de depuración que libera la memoria y deshabilítelo cuando realice el envío.


Consigamos nuestras definiciones correctas, primero. Una pérdida de memoria es cuando la memoria se asigna dinámicamente, por ejemplo, con malloc() , y todas las referencias a la memoria se pierden sin la correspondiente libertad. Una forma fácil de hacer uno es así:

#define BLK ((size_t)1024)
while(1){
    void * vp = malloc(BLK);
}

Tenga en cuenta que cada vez que se recurre al bucle while (1), se asignan 1024 (+ sobrecarga) bytes, y la nueva dirección se asigna a vp; no hay puntero restante a los bloques malloc'ed anteriores. Se garantiza que este programa se ejecutará hasta que se agote el montón, y no hay forma de recuperar nada de la memoria de malloc'ed. La memoria se está "escapando" del montón, para no ser vista nunca más.

Sin embargo, lo que estás describiendo suena como

int main(){
    void * vp = malloc(LOTS);
    // Go do something useful
    return 0;
}

Usted asigna la memoria, trabaje con ella hasta que el programa termine. Esto no es una pérdida de memoria; no daña el programa y toda la memoria se eliminará automáticamente cuando el programa termine.

En general, debe evitar las fugas de memoria. Primero, porque al igual que la altitud por encima de ti y el combustible en el hangar, la memoria que se filtró y no se puede recuperar es inútil; En segundo lugar, es mucho más fácil codificar correctamente, sin perder memoria, al principio que encontrar una pérdida de memoria más tarde.


Generalmente, una pérdida de memoria en una aplicación independiente no es fatal, ya que se limpia cuando el programa sale.

¿Qué hace usted para los programas del servidor que están diseñados para que no salgan?

Si usted es el tipo de programador que no diseña e implementa el código donde los recursos se asignan y liberan correctamente, entonces no quiero tener nada que ver con usted o su código. Si no te importa limpiar tu memoria filtrada, ¿qué hay de tus cerraduras? ¿Los dejas colgando por ahí también? ¿Dejas pequeños montones de archivos temporales en varios directorios?

¿Perder esa memoria y dejar que el programa la limpie? No absolutamente no. Es un mal hábito, que conduce a errores, errores y más errores.

Limpiar después de ti mismo Yo mamá ya no trabaja aquí.


Voy a responder que no.

En teoría, el sistema operativo se limpiará después de usted si deja un desastre (ahora eso es simplemente grosero, pero dado que las computadoras no tienen sentimientos, podría ser aceptable). Pero no puede anticipar cada situación posible que pueda ocurrir cuando se ejecuta su programa. Por lo tanto (a menos que sea capaz de realizar una prueba formal de algún comportamiento), crear pérdidas de memoria es simplemente irresponsable y descuidado desde un punto de vista profesional.

Si un componente de un tercero pierde memoria, este es un argumento muy fuerte en contra del uso, no solo por el efecto inminente sino también porque muestra que los programadores trabajan de manera descuidada y que esto también podría afectar otras métricas. Ahora, cuando se consideran los sistemas heredados, esto es difícil (considere los componentes de navegación web: que yo sepa, todos pierden memoria) pero debería ser la norma.


Esto ya fue discutido ad nauseam . La conclusión es que una pérdida de memoria es un error y debe solucionarse. Si una biblioteca de terceros pierde memoria, eso hace que uno se pregunte qué más hay de malo, ¿no? Si estuviera construyendo un automóvil, ¿usaría un motor que ocasionalmente gotea aceite? Después de todo, alguien más hizo el motor, así que no es tu culpa y no puedes arreglarlo, ¿verdad?


Estoy de acuerdo con vfilby - depende En Windows, tratamos las fugas de memoria como errores relativamente serios. Pero, depende mucho del componente.

Por ejemplo, las fugas de memoria no son muy graves para los componentes que se ejecutan con poca frecuencia y por períodos de tiempo limitados. Estos componentes se ejecutan, hacen su trabajo, luego salen. Cuando salen todos sus recuerdos se liberan implícitamente.

Sin embargo, las fugas de memoria en los servicios u otros componentes de ejecución prolongada (como el shell) son muy graves. La razón es que estos errores "roban" la memoria con el tiempo. La única forma de recuperar esto es reiniciar los componentes. La mayoría de las personas no saben cómo reiniciar un servicio o el shell, por lo que si el rendimiento de su sistema se resiente, solo se reinicia.

Entonces, si tiene una fuga, evalúe su impacto de dos maneras

  1. Para su software y la experiencia de su usuario.
  2. Al sistema (y al usuario) en términos de ser frugales con los recursos del sistema.
  3. Impacto de la corrección en el mantenimiento y la fiabilidad.
  4. Probabilidad de causar una regresión en otro lugar.

Foredecker


No, no debería haber fugas que el sistema operativo limpie para usted. La razón (no mencionada en las respuestas anteriores hasta donde pude verificar) es que nunca se sabe cuándo se volverá a usar su main () como función / módulo en otro programa . Si su main () se convierte en una función llamada con frecuencia en el software de otra persona, este software tendrá una pérdida de memoria que se come la memoria con el tiempo.

KIV


Realmente no es una fuga si es intencional y no es un problema a menos que sea una cantidad significativa de memoria, o podría llegar a ser una cantidad significativa de memoria. Es bastante común no limpiar las asignaciones globales durante la vida útil de un programa. Si la fuga está en un servidor o una aplicación de larga duración, crece con el tiempo, entonces es un problema.





memory-leaks