c++ - repentina - se caracteriza por la perdida de memoria inmediata
Diferentes formas de perder memoria (7)
- Asignando memoria con
new/new[]/malloc
y no liberándola - Asignar memoria a un puntero y sobrescribirlo accidentalmente
p = new int; p = new int[1];
Ej.,p = new int; p = new int[1];
p = new int; p = new int[1];
- Asignando memoria en el aire. es decir,
new int[100];
ocout<<(*new string("hello"))<<endl;
El concepto básico de pérdida de memoria es una falta de coincidencia entre una operación nueva / eliminar durante la ejecución del código, ya sea debido a prácticas de codificación incorrectas o en casos de errores cuando se omite la operación de eliminación.
Pero recientemente me hicieron una pregunta en una entrevista sobre otras formas en que la memoria puede filtrarse. No tenía respuesta. ¿Qué es?
Como una pregunta de entrevista, el entrevistador puede haber estado buscando una perspectiva más amplia que el libro de texto nuevo / eliminar desajuste.
Cualquier memoria que persista más allá del último punto que se necesita puede considerarse "filtrada". Esta memoria puede eventualmente liberarse manualmente con una eliminación más abajo en el código, haciendo que estas fugas sean temporales en lugar de las fugas permanentes que encuentra con operadores nuevos / eliminados que no coinciden. Sin embargo, durante el tiempo que la "fuga" persista, el efecto neto es el mismo. Está reduciendo la cantidad de recursos disponibles (memoria) a otras partes de su programa.
En el código recogido de basura, la memoria se considera filtrada si continúa reteniendo referencias a un objeto que ya no necesita, evitando que el recolector de basura lo reclame. Si retiene los objetos innecesarios indefinidamente, ha creado una fuga permanente en el código recolectado.
La fragmentación podría ser uno de los problemas que causan falta de memoria. Después de una ejecución prolongada de sus programas, puede provocar la fragmentación de la memoria.
Siempre hay mi favorito, la "fuga de memoria sin fugas", donde su programa retiene correctamente los punteros a la memoria asignada, pero (por cualquier razón) nunca logran liberar la memoria a la que apuntan:
// Maybe not technically a memory leak, but it might as well be one
static vector<const char *> strings;
void StoreTheString(const char * str)
{
strings.push_back(strdup(str));
}
Es doblemente molesto porque los programas de detector de fuga de memoria no lo reconocerán como una pérdida de memoria (porque los punteros aún son alcanzables) y, sin embargo, su programa consumirá memoria hasta que falle.
Este tipo de problema puede ocurrir incluso en lenguajes recolectados con basura como Java.
Utilizo el recopilador de basura Boehm con el operador sobrecargado new y delete, y que funciona perfectamente para cualquier clase compilada con él, pero también falla horriblemente con std :: string y algunos contenedores STL, incluso si las variables son miembros de un basura recogida clase. Tuve algunas pérdidas de memoria hasta que me di cuenta. Afortunadamente proporciona un asignador recolectado de basura.
También recuerdo un auto sin conductor que fue programado en Java, pero se estrelló * cada 20 a 40 minutos. Recolectó información de detección de objetos sobre varios pedazos de sotobosque y basura que encontró a lo largo de la carretera, los suscribió a alguna cola, ... y nunca los eliminó.
*: Mira lo que hice allí: D
Los problemas comunes de memoria dinámica son:
- Asignación dinámica de memoria con
new
y no desasignación condelete
. - Asignación dinámica de memoria con
new[]
y desasignación condelete
. - Asignación de memoria dinámica
new
y desasignar confree
. - Asignación de memoria dinámica
malloc
y desasignarlo condelete
.
Además de las pérdidas de memoria / corrupción de memoria, los últimos 3 escenarios provocarán el temido Comportamiento no definido .
Algunos otros posibles escenarios de pérdida de memoria que puedo recordar son:
- Si un puntero que apunta a una región de memoria asignada dinámicamente se reasigna a un nuevo valor antes de ser desasignado, dará lugar a un puntero colgante y una pérdida de memoria.
Un ejemplo de código:
char *a = new[128];
char *b = new[128];
b = a;
delete[]a;
delete[]b; // will not deallocate the pointer to the original allocated memory.
- Punteros en contenedores STL
Un escenario más común y frecuente es, Almacenamiento de punteros que apuntan a tipos asignados dinámicamente en contenedores STL. Es importante tener en cuenta que los contenedores STL toman la propiedad de eliminar el objeto contenido solo si no es un tipo de puntero .
Uno tiene que iterar explícitamente a través del contenedor y eliminar cada tipo contenido antes de eliminar el contenedor. No hacerlo causa una pérdida de memoria.
Aquí hay un ejemplo de tal escenario.
- El problema del destructor de la clase base no virtual
Eliminar un puntero a la clase Base que apunta a cualquier objeto dinámicamente asignado de la clase derivada en el montón. Esto da como resultado un Comportamiento Indefinido.
Un ejemplo de código:
class MyClass
{
public:
virtual void doSomething(){}
};
class MyClass2 : public MyClass
{
private:
std::string str;
public: MyClass2( std::string& s)
{
str=s;
}
virtual void doSomething(){}
};
int main()
{
std::str hello("hello");
MyClass * p = new MyClass2(hello);
if( p )
{
delete p;
}
return 0;
}
En el ejemplo, solo se llama al destructor MyClass :: ~ MyClass () y nunca se llama a MyClass2 :: ~ MyClass2 (). Para la desasignación apropiada uno necesitaría,
MyClass::virtual ~MyClass(){}
- Llamando delete
en un puntero de void
Un ejemplo de código:
void doSomething( void * p )
{
//do something interesting
if(p)
delete p;
}
int main()
{
A* p = new A();
doSomething(p);
return 0;
}
Llamar a delete
en un puntero de void
como en el ejemplo anterior, causará una pérdida de memoria y un comportamiento indefinido.
Otro escenario es cuando se usan punteros inteligentes de recuento de referencias, como boost::shared_ptr
, que comúnmente se piensa que "eliminan fugas de memoria" pero se crean referencias cíclicas.
¿Cómo evitar la pérdida de memoria con shared_ptr?