memoria - limpiar variables en c++
Pérdida de memoria en C, C++; se olvidó de hacer libre, eliminar (18)
Actualización 1:
Si tiene una aplicación simple que se ejecuta una vez, hace su trabajo y termina, entonces una pérdida de memoria no es tan importante. Todavía es una práctica muy mala y si el estilo de codificación es tal que permite fugas en su código, es probable que coloque las mismas fugas en las aplicaciones donde es importante: las que funcionan durante días, semanas o años. Tenemos una aplicación aquí que gotea, así que la reiniciamos cada mes. No es una situación ideal.
Actualización 2:
si, bastante Pero las fugas de memoria deben evitarse simplemente porque son un error, y nunca debe escribir código con la visión de que los errores son aceptables.
Actualización 3:
La mejor manera de prevenir las fugas de memoria es no usar malloc / free en primer lugar. Si está utilizando la lectura de C ++ en RAII, use clases y copie los objetos, esto asegurará que nunca tenga fugas ... casi todo el tiempo. Si tiene que asignar memoria explícitamente, entonces asegúrese de mantenerla al tanto. Si eso significa que necesita un objeto global en algún lugar donde almacene los punteros, hágalo. Si significa que tiene una clase de colección que almacena los punteros, entonces obtenga uno. Nunca asigne memoria a una variable local de la que pueda olvidarse, podría regresar de una función sin pasar por la llamada gratuita, podría pasar a otra función para liberarla que no recibe llamadas. Se necesita un sentido de disciplina para esto (no se requiere mucha disciplina), pero muchas personas le dirán que esa misma virtud es necesaria para escribir un código bueno, correcto, bien diseñado y libre de errores de todos modos (y tendrían razón: Si alguna vez ha visto un código pirateado junto con un código bien diseñado, podrá ver la diferencia de inmediato.
Notas:
incluso utilizando un lenguaje recolectado en la basura, todavía obtendrá pérdidas de memoria. Las personas agregan objetos a las colecciones, luego se olvidan de eliminarlos y el objeto permanece en la memoria para siempre. Eso cuenta como una fuga. Es razonablemente común en los idiomas de GC hacer esto ya que la gente cree que el GC hará todo el trabajo por ellos. Nuevamente, se requiere disciplina de diseño / codificación: saber lo que estás haciendo evitará estos errores.
Las fugas de memoria pueden ocurrir incluso sin usar malloc / new. Tenga en cuenta que las variables de puntero sobrescriben que ya apuntan a alguna memoria. Mi mayor fuente de fugas fue con MSXML. Creé un CComPtr de un objeto XML, luego llamé al método para obtener un elemento y pasé el objeto al método como un parámetro. Desafortunadamente, la clase se colocó en el puntero interno y el método simplemente lo sobreescribió con el nuevo puntero, dejando los datos antiguos filtrados. La moraleja aquí es que, si utiliza una clase de puntero inteligente, asegúrese de saber qué está haciendo, especialmente con su operador de yeso.
Herramientas:
no es necesario comprar Purify si se ejecuta en Windows. Microsoft ofrece UMDH que toma instantáneas de su memoria. Tome 2 instantáneas y compárelas con la herramienta, y podrá ver las asignaciones que aumentan constantemente con el tiempo sin que se desasignen. No es bonito, pero funciona.
Asignamos memoria en C usando malloc y en C ++ usando nuevo. Sé que la memoria asignada debe liberarse o devolverse al sistema operativo usando free en C y eliminar en C ++. Si olvidé usar free / delete después de asignar memoria, significa que habrá una pérdida de memoria.
Ahora, mi pregunta es si esta pérdida de memoria solo durante el período de tiempo de ejecución del programa; ¿o es una fuga / pérdida permanente o se gana nuevamente una vez que reinicie el sistema? ¿Cuál es el proceso interno en realidad? ¿Qué significa exactamente la pérdida / pérdida de memoria?
Estaría muy agradecido si alguien pudiera explicar esto en detalle o proporcionarme algunas buenas referencias.
ACTUALIZACIÓN 1
Después de leer algunas respuestas, aprendí que la memoria se devuelve al sistema operativo / sistema después de que el programa finaliza, si es así, ¿por qué todos necesitan preocuparse tanto por la pérdida de memoria y por qué es muy importante prevenir la pérdida de memoria?
ACTUALIZACIÓN 2
Entonces, ¿debería evitarse la pérdida de memoria para que el sistema no se bloquee debido a la falta de memoria suficiente para fines de asignación?
ACTUALIZACIÓN 3
Entonces, después de leer todas las respuestas, me di cuenta de que la pérdida de memoria es un tema importante para evitar fallos del sistema. Pero, para un principiante como yo, ¿cómo puedo estar seguro de que mi programa está completamente libre de pérdida de memoria? Intento hacerlo gratis, eliminar si estoy usando malloc, nuevo, pero a veces se complica. ¿Existe alguna herramienta o método que pueda usar para saber si mi programa está realizando alguna pérdida de memoria?
ACTUALIZACIÓN 4
Después de leer las respuestas, ahora he entendido la importancia del código libre de pérdida de memoria, menos uso de nuevo / eliminar, más uso de STL, aprendí nuevas cosas como RAII, valgrind y buenas prácticas de programación. Gracias a todos :)
Como nuevo programador de C ++, el mejor consejo que podría darte sería aprender a minimizar el número de declaraciones "nuevas" y "eliminadas" que escribes. Si el compilador crea sus objetos localmente en la pila, administrará la memoria por usted, eliminando los objetos automáticamente a medida que queden fuera del alcance.
Hay una idea de programación llamada La adquisición de recursos es la inicialización ( RAII ). Lo que esto dice es "si necesita asignar memoria que necesita ser eliminada, o asegurarse de que las cosas que abre se cierren, envuélvalas en un objeto que cree en la pila. De esa manera, cuando el objeto salga del alcance, el destructor Se invoca automáticamente, y eliminas tu recurso en el destructor ".
Las pérdidas de memoria comunes ocurren cuando escribe un "nuevo" en el código, pero su función se cierra antes de llamar a eliminar. A veces, encuentra una declaración de "devolución" demasiado pronto, otras veces se lanza una excepción después de su declaración de "eliminación". Seguir a RAII te ayuda a asegurarte de que esos accidentes no ocurran.
Cuando el programa termina en Windows, libera no solo la memoria sino todos los controladores abiertos (corríjame si me equivoco)
El sistema operativo rastreará la memoria y, una vez que finalice su programa, recuperará toda la memoria. Solo significa que su aplicación perdió la pista de alguna memoria asignada.
Tenga en cuenta que esto podría no aplicarse en algunos sistemas operativos, pero será el caso en cualquier sistema de tipo windows / unix / mac
En respuesta a tu pregunta, y actualización 1 ,:
No todos los sistemas operativos admiten la noción de procesos distintos, por lo tanto, nunca limpiarán las cosas automáticamente.
Por ejemplo, un sistema operativo integrado como VxWorks (en algunas configuraciones) no admite el concepto de procesos múltiples, por lo que incluso después de que finalice su tarea, quedará cualquier memoria que no haya podido cancelar la asignación. Si eso no fue pensado, terminarás con una pérdida de memoria.
Además, tal plataforma probablemente se usa en un sistema que rara vez se reinicia y no admite el intercambio, por lo que cualquier pérdida de memoria es mucho más grave de lo que sería en un escritorio (por ejemplo).
La forma correcta de evitar las fugas de memoria es realizar una administración de memoria menos explícita y confiar en contenedores que administran las cosas por usted, por ejemplo, STL en C ++ (partes relevantes de la misma).
Los programadores integrados de bajo nivel que utilizan C a menudo evitan las fugas de memoria al asignar de forma estática todo al inicio. Los programadores de C también pueden usar la asignación de pila con alloca () para evitar posibles pérdidas de memoria (pero necesitan entender exactamente cómo funciona).
Es por proceso . Una vez que el proceso sale, la memoria asignada se devuelve al sistema operativo para que la utilicen otros procesos (nuevos o existentes).
Para responder a su pregunta editada, solo hay una cantidad finita de memoria en su máquina. Entonces, si tiene una pérdida de memoria, entonces el problema principal es que la memoria no está disponible para que otros procesos la utilicen. Un efecto secundario, pero no despreciable, es que su imagen de proceso crece, cambiará a disco y el rendimiento se verá afectado. Finalmente, su programa agotará toda la memoria del sistema y fallará, ya que no puede asignar ninguna memoria para sí mismo.
Se puede argumentar que para un proceso pequeño con una vida útil corta, las pérdidas de memoria son tolerables, ya que la memoria filtrada será pequeña en cantidad y de corta duración.
Eche un vistazo a este recurso , para obtener posiblemente más información de la que necesitará. Lo que estamos discutiendo aquí es la asignación dinámica o de pila .
Es la memoria asignada al proceso. Lo recuperarás cuando mates el proceso.
Es una pérdida de memoria.
Básicamente, lo que significa es que esta memoria no se recuperará hasta que se destruya el proceso.
El problema es que cuando el puntero se sale del alcance y no libera la memoria, el proceso la asigna, pero el programa no tiene forma de saber que está fuera del alcance y ya no es necesario (sin usar una herramienta como Valgrind).
Este es solo un problema importante si sucede repetidamente. Si lo hace, entonces el programa seguirá utilizando más y más memoria a medida que se ejecute antes de que se bloquee. Los usuarios deberán reiniciar la aplicación con regularidad para evitar que esto ocurra o si utiliza demasiados recursos del sistema.
Existen herramientas para detectar fugas de memoria, por ejemplo, Purify .
Junto con Purify, puedes probar una alternativa gratuita: valgrind. La estipulación es que valgrind es una solución específica de Linux.
La memoria no se perderá pero permanecerá asignada y, por lo tanto, no estará disponible para las próximas asignaciones que haga su programa. Esto significa que su programa consume más y más memoria si continúa asignando memoria sin desasignarla. Después de un tiempo, queda memoria sin asignar y el siguiente intento de asignar una nueva memoria fallará, así que su programa lo hace.
Esta memoria se toma del llamado "montón". El cual es local para su programa y se elimina completamente cuando su programa termina. Por lo tanto, el "único" daño que su programa puede hacer a los otros programas que se ejecutan en el sistema y el sistema operativo es que también pueden ser incapaces de asignar memoria porque su programa se ha "comido" todo. Tan pronto como finalice su programa, otros deberían ejecutarse normalmente si no se hubieran bloqueado mientras tanto debido a los problemas de asignación.
Las pérdidas de memoria normalmente causan problemas para los programas de ejecución prolongada; las fugas de unos pocos bytes en lugares desafortunados como los bucles pueden expandir rápidamente la huella de memoria de su aplicación.
Para responder a la actualización 3, generalmente hay una manera de indicar si su proceso tiene asignaciones de memoria pendientes. _Heapwalk (bajo Win32) le permitirá pasar por todas sus asignaciones y puede ver si hay alguna pendiente.
Más allá de eso, es probable que valga la pena ajustar sus llamadas malloc / new para que registre el archivo y el número de línea de cada asignación cuando suceda. Luego, en su eliminación / eliminación anulada, la elimina de la lista.
p. ej. (Tenga en cuenta que este es un código totalmente no probado, por lo que probablemente no funcionará de inmediato)
struct MemoryAllocEntry
{
char* pFile;
char* pLine;
};
extern std::map< MemoryAllocEntry > g_AllocList;
inline void* MyMemAlloc( size_t size, char* pFile, char* pLine )
{
MemoryAllocEntry mae;
void* pRet = malloc( size );
mae.pFile = pFile;
mae.pLine = pLine;
g_AllocList[pRet] = mae;
return pRet;
}
inline void MyMemFree( void* pPtr )
{
std::map< MemoryAllocEntry >::iterator iter = g_AllocList.find( pPtr );
if ( iter != g_AllocList.end() )
{
g_AllocList.erase( iter );
}
free( pPtr );
}
#ifdef _DEBUG
#define malloc( x ) MyMemAlloc( (x), __FILE__, __LINE__ )
#define free( x ) MyMemFree( (x) )
#endif
Luego, todo lo que necesita hacer es pasar por g_AllocList para encontrar las asignaciones pendientes. Lo anterior, obviamente, solo funciona para malloc y gratis, pero también puede hacerlo para nuevo y eliminar (MFC lo hace, por ejemplo).
Re: herramientas para detectar pérdida de memoria
Si usa un sistema operativo basado en Linux para el desarrollo, puede intentar usar valgrind ( http://valgrind.org/ ) para detectar la pérdida de memoria.
valgrind --leak-check=full ./compiled_binary
Si su programa se compiló con símbolos de depuración (por ejemplo, para gcc, incluya el indicador -g), valgrind también le informará la línea exacta de código donde se asignó la memoria filtrada. Esto facilitará enormemente la tarea de seguimiento y reparación de fugas.
Pros: es gratis
Contras: AFAIK, solo funciona en Linux
Actualizar
Como se ve en http://valgrind.org/info/platforms.html , valgrind se está portando a otros sistemas operativos (y plataformas) que incluyen MacOSX, FreeBSD y NetBSD.
Actualización 2
(ligeramente fuera del tema pero ...)
Lo bueno de usar valgrind es que hace mucho más que solo buscar fugas de memoria. Ver http://valgrind.org/info/tools.html
Configuré buildbot para ejecutar valgrind (y férula) contra todas mis compilaciones nocturnas, ¡y eso ha demostrado ser invaluable!
Respondiendo a la edición -
Si su programa está destinado a ejecutarse, así que algo y luego termina entonces no, no tienes que preocuparte demasiado por liberar memoria. Es importante para los programas que se ejecutan por un tiempo. Si su navegador web no liberara la memoria que usaba para mostrar una página, pronto usaría toda la memoria de su computadora.
Sin embargo, es una buena práctica liberar la memoria, ya que los programas pequeños que se ejecutan una vez tienen el hábito de convertirse en otras cosas y es un buen hábito.
Una herramienta para controlar las fugas de memoria es anular los operadores nuevos y eliminados. Esto le permite mantener una lista de memoria que se ha asignado y no se ha liberado. Por lo tanto, si un objeto en particular debería haber liberado toda la memoria que está utilizando, este mecanismo le proporciona una forma de verificar que realmente ha liberado la memoria.
Una pérdida de memoria simplemente significa que su aplicación no puede liberar la memoria que ha asignado. Una vez que su programa finaliza, depende del sistema operativo lo que sucede. Todos los sistemas operativos modernos recuperarán toda la memoria utilizada por la aplicación, por lo que una vez que finalice su proceso, se limpiará.
Pero C / C ++ no garantiza que el sistema operativo haga eso. Es posible que en algunas plataformas la memoria se pierda hasta que reinicie.
Así que el problema con las fugas de memoria es doble:
- Es posible que deba reiniciar el sistema para recuperar la memoria. En la mayoría de las plataformas, esto no es un problema, aunque filtrar algunos otros tipos de recursos aún puede causar problemas.
- Mientras su programa se ejecute, asignará memoria que nunca libera, lo que significa que utilizará más y más memoria. Si su programa está diseñado para ejecutarse durante un tiempo prolongado, puede terminar utilizando toda la memoria disponible en la máquina y luego bloquearse.
Muchos programas de ejecución corta en realidad ignoran las fugas de memoria porque saben que el sistema operativo las limpiará muy pronto. El compilador de C ++ de Microsoft hace esto, que yo sepa. Saben que una vez que se invoca el compilador, se ejecuta durante un par de minutos como máximo. (y saben que se ejecuta en Windows, donde el sistema operativo recupera la memoria una vez que finaliza el proceso) Así que está bien que pierda algo de memoria aquí y allá.
En cuanto a cómo evitar las fugas de memoria, no las cree.
Se arriesga a perder memoria cada vez que usa nuevo / eliminar, así que no lo haga .
Cuando necesite una matriz de datos haga esto:
std::vector<char> vec(200);
en lugar de esto:
char* arr = new char[200];
El primero es igual de eficiente, pero no tiene que llamar explícitamente a delete para liberarlo. std::vector
usa RAII para administrar sus recursos internamente. Y debe hacer lo mismo, ya sea utilizando clases RAII ya hechas como vector
, shared_ptr
o casi cualquier otra clase en la biblioteca estándar o en Boost, o escribiendo la suya propia.
Como regla general, su código no debe contener ninguna llamada nueva / eliminar, excepto en el constructor / destructor para la clase responsable de administrar esa asignación.
Si un objeto asigna la memoria que necesita en el constructor y la libera en el destructor (y maneja la copia / asignación correctamente), entonces puede simplemente crear una instancia local de la clase en la pila cuando la necesite, y no lo hará. , no se puede, fuga de memoria.
La clave para no perder memoria en C ++ es no llamar a new / delete.
Varios puntos para añadir:
- Aprender a trabajar correctamente desde el principio : la memoria libre, es muy difícil arreglar el mal hábito.
La memoria no es el recurso único que se debe administrar o administrar usando new / delete.
Por ejemplo, algún objeto puede contener algún archivo temporal que debería eliminarse al final. Por lo general, estas cosas están vinculadas a algún objeto, por lo tanto, si olvida eliminar el objeto, olvide desvincular el archivo y ... Este recurso no vuelve incluso con el reinicio del sistema.
Hay muchos otros recursos similares que se unieron a objetos y se administraron con nuevos / eliminar: archivos, sockets, memoria compartida, conexiones de base de datos, etc. Por lo tanto, todas las técnicas que aprenda a administrar la memoria lo ayudarán a administrar estos otros recursos, muy limitados, que tendría que usar.