Fuga de memoria C++

1. ¿Qué es una pérdida de memoria?

La fuga de memoria se refiere a la memoria que se aplica dinámicamente en el código del programa, y ​​la memoria en el montón

Una pequeña cantidad de fugas de memoria no afectará el funcionamiento normal del programa, pero si se trata de una fuga de memoria persistente, la memoria del sistema se consumirá y, finalmente, el programa se congelará o incluso el sistema colapsará. Para evitar el bloqueo del sistema, cuando no se puede asignar la memoria, es necesario llamar a la función exit () para eliminar activamente el proceso en lugar de intentar guardar el proceso.

2. Cómo detectar fugas de memoria

Si la memoria ocupada por el programa continúa creciendo con el tiempo durante el uso normal del programa, generalmente indica que hay una pérdida de memoria. También es posible utilizar herramientas especializadas para detectar pérdidas de memoria en los programas:

En vc++, se puede usar VLD (Visual LeakDetector) para la detección.VLD es una herramienta gratuita y de código abierto que solo necesita incluir el archivo de encabezado y puede obtener el número de línea del archivo de código de la fuga de memoria.

Tencent tMem Monitor es una herramienta de detección de fugas de memoria C/C++ en tiempo de ejecución lanzada por Tencent. TMM cree que cuando el proceso finaliza, los bloques de memoria sin propietario en la memoria del montón que no se liberan y no tienen punteros son fugas de memoria, y luego introducen un mecanismo de recolección de elementos no utilizados (GC, Garbage Collection) para detectar toda la memoria del montón cuando finaliza el proceso. No hay celdas de memoria a las que se hace referencia, por lo que la precisión de detección de fugas de memoria es del 100 %.

gperftools es un conjunto de suites de Google de código abierto que proporciona una implementación de malloc de subprocesos múltiples de alto rendimiento, así como un conjunto de excelentes herramientas de análisis de rendimiento. El componente heap cracker de gperftools puede usarse para detectar fugas de memoria en programas C++ y puede realizar comprobaciones de fugas de memoria sin reiniciar el programa.

3. ¿Cómo ocurre una fuga de memoria?

La explicación más simple es que el bloque de memoria solicitado activamente no se libera después de su uso. Las causas más comunes de pérdidas de memoria son:

1. La memoria solicitada por malloc/new no se libera activamente

La memoria aplicada por malloc debería llamar activamente a free, y la memoria aplicada por new debería llamar activamente a delete, de lo contrario, provocará pérdidas de memoria. Por ejemplo, el ptr de memoria en el siguiente código no se libera después de la aplicación, lo que provoca una pérdida de memoria.

int main(){
	void* ptr = malloc(1);
	// use ptr ...
	return 0;
}

2. Use gratis para liberar la memoria solicitada por nuevos

malloc/free y new/delete deben aparecer en parejas, si se mezclan se producirán situaciones inesperadas. Además, si el objeto al que apunta el puntero de vacío se libera con eliminar, también provocará una pérdida de memoria.

3. Use eliminar para eliminar una matriz

Use delete[] para eliminar la matriz aplicada por new. Si la elimina por error, provocará una pérdida de memoria.

int main(){
	int* ptr = new int[2];
	// usr ptr ...
	// delete ptr; // 错误!释放数组要用 delete[]
	delete[] ptr; // 正确!
	return 0;
}

4. El destructor de la clase base no está definido como una función virtual

Cuando el puntero de la clase base apunta al objeto de la subclase, si el destructor de la clase base no es virtual, no se llamará al destructor de la subclase y los recursos de la subclase no se liberan correctamente, lo que provoca pérdidas de memoria. Por ejemplo, el destructor ~A() en el siguiente código no es virtual. Cuando se llama a delete pa, no se llamará al destructor de la subclase B, y la memoria en la subclase B en este objeto no se puede liberar limpiamente.

class A
{
public:
	A(){}
	~A(){}
}

class B : public A 
{
public:
	B(){}
	~B(){}
private:
	int num;
}

void main(){
	A* pa = new B();
	delete pa;
}

Cuarto, cómo evitar pérdidas de memoria

Las fugas de memoria pueden conducir a la inestabilidad del programa. Si está investigando una fuga de memoria en un proyecto muy complejo, también es algo muy problemático. En lugar de estudiar cómo resolver mejor el problema, es mejor estudiar cómo evitarlo. sucedió.

A menudo es una forma más eficiente de dedicar más tiempo a escribir código para garantizar la calidad del código. En la etapa inicial, si el código escrito tiene prisa para ponerse al día con el progreso, llevará más tiempo llenar el vacío en la etapa posterior, que es "la prisa no es suficiente".

Para evitar situaciones de pérdida de memoria, hay varias formas de intentarlo.

1. Usa la memoria dinámica con moderación

Al escribir código, esté atento a la memoria dinámica y asegúrese de que se libere cada bloque de memoria solicitado. Especialmente antes de cada regreso, piense si todavía hay memoria que no se ha liberado y, si no se libera aquí, si se liberará normalmente en otro lugar.

Esta es una forma de confiar en la cabeza, y debe ser sensible al escribir código, pero la cabeza a menudo no es confiable. Es mejor usar otros métodos para proteger.

2. Usando RAII

RAII, el nombre completo de Resource Acquisition Is Initialization (inglés: Resource Acquisition Is Initialization), realiza la adquisición de recursos a través de la inicialización de objetos y realiza la liberación de recursos a través de la destrucción de objetos. Los recursos que llamamos son memoria dinámica. RAII requiere que el período de validez de un recurso esté estrictamente ligado al ciclo de vida del objeto que contiene el recurso. El recurso se obtiene a través del constructor y se libera a través del destructor, evitando así la fuga de recursos.

Por ejemplo, en el siguiente ejemplo, la memoria se asigna a través del constructor de la clase MemBlock, y la memoria se libera a través del destructor. Donde se necesita usar memoria dinámica, solo se necesita definir un beneficio de objeto MemBlock, y la memoria La dirección se obtiene a través de la función miembro get (). Liberar manualmente la memoria. Cuando el buff abandona el alcance, el buff se liberará automáticamente (llame al destructor) y llame al destructor para liberar la memoria retenida por el buff. Si usa este método en todos los lugares que necesitan usar memoria, siempre que se asegure de que el objeto MemBlock se pueda destruir, no habrá pérdida de memoria.

class MemBlock
{
public:
	MemBlock(size_t size) 
	{
		_ptr = malloc(size);
	}
	~MemBlock()
	{
		free(_ptr);
	}
public:
	void* ptr() 
	{
		return _ptr;
	}
protected:
	void* _ptr;
};

void main(){
	MemBlock buff(1024);
	memset(buff.ptr(), 0x00, 1024);
	// 使用内存后无需主动释放
}

Por supuesto, si solicita un objeto MemBlock a través de nuevo, existirá el riesgo de que el objeto no se libere. En este momento, será una buena opción usar un puntero inteligente para archivar el objeto MemBlock.

3. Usa punteros inteligentes

Es para resolver el problema de seguridad del uso de la memoria dinámica, C++ introdujo el concepto de puntero inteligente. Además de todas las funciones de la memoria ordinaria, el puntero inteligente también puede garantizar que el objeto señalado se libere automáticamente cuando ya no se haga referencia a él. . De esta forma, los desarrolladores de C++ pueden usar punteros inteligentes para quitar la espada de Damocles de encima.

Hay dos punteros inteligentes shared_ptr y unique_ptr proporcionados por la biblioteca estándar. La diferencia entre los dos radica en el método de gestión del puntero subyacente. Shared_ptr permite que varios punteros apunten al mismo objeto. Internamente, el recuento de referencias sabe que el objeto es referenciado por varios punteros.Cuando la referencia es 0 El momento es cuando se liberará el objeto, el unique_ptr apunta "exclusivamente" al objeto, no se puede asignar, y la inteligencia transfiere la referencia a otro unique_ptr a través de std::move( ). La biblioteca estándar también define una clase complementaria llamada débil_ptr, que es una referencia débil que apunta al objeto administrado por shared_ptr como observador y no cambia el recuento de referencias del objeto. Estos tres punteros inteligentes se definen en el archivo de encabezado de memoria. Use punteros inteligentes para administrar directamente la memoria dinámica , que no necesita liberarse manualmente después de su uso. Cuando ya no se haga referencia a esta memoria, esta memoria se liberará llamando a la función gratuita. La función gratuita se pasa a la función inteligente como una costumbre función de liberación Si el puntero es un objeto de otro tipo, no necesita pasar la función de liberación, y el destructor del tipo se llamará de forma predeterminada para liberarlo.

shared_ptr<void> ptr(malloc(1024), free); // 传入指定的释放函数
//auto ptr = make_shared<void>(malloc(1024), free); // 等价
memset(ptr.get(), 0x00, 1024);

Use punteros inteligentes combinados con RAII para administrar la memoria dinámica , encapsule la aplicación y la liberación de memoria a través de RAII, y luego use punteros inteligentes para administrar los objetos de clase encapsulados. De esta forma, se realiza la gestión automática de la memoria y la memoria se puede utilizar como C# o Java sin preocuparse por la liberación de la memoria. Combinado con la clase MemBlock definida anteriormente:

auto mem = make_shared<MemBlock>(1024);
memset(mem->ptr(), 0x00, 1024);
// 使用内存后无需主动释放

La aplicación y liberación de memoria se realiza a través del constructor y destructor de la clase MemBlock. El objeto mem de la clase MemBlock es administrado por punteros inteligentes. Cuando la memoria ya no se usa, el recuento de referencias de mem se convierte en 0 y se liberado y destruido automáticamente, y liberado al mismo tiempo, su propia memoria.

Supongo que te gusta

Origin blog.csdn.net/Ango_/article/details/123929266
Recomendado
Clasificación