Compartir a través de


Búsqueda de fugas de memoria con la biblioteca de CRT

Las fugas de memoria se encuentran entre los errores más sutiles y difíciles de detectar en aplicaciones C/C++. Las fugas de memoria son consecuencia de un error al desasignar correctamente la memoria que previamente se había asignado. Es posible que no se detecte una pequeña fuga de memoria al principio, pero con el tiempo puede provocar síntomas que van desde un rendimiento deficiente hasta el bloqueo cuando la aplicación se queda sin memoria. Una aplicación con fugas de memoria que utilice toda la memoria disponible puede provocar el bloqueo de otras aplicaciones y crear confusión respecto a qué aplicación es la responsable. Incluso unas fugas de memoria inocuas podrían indicar otros problemas que se deben corregir.

El depurador de Visual Studio y la biblioteca en tiempo de ejecución de C (CRT) pueden ayudarle a detectar e identificar las fugas de memoria.

Habilitación de la detección de fugas de memoria

Las herramientas principales para detectar fugas de memoria son el depurador de C/C++ y las funciones del montón de depuración de CRT.

Para habilitar todas las funciones del montón de depuración, incluya las siguientes instrucciones en el programa de C++, en el orden siguiente:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

La instrucción #define asigna una versión base de las funciones del montón de CRT a la versión de depuración correspondiente. Si omite la instrucción #define, el volcado de fuga de memoria será menos detallado.

La inclusión de crtdbg.h asigna las malloc funciones y free a sus versiones de depuración y _malloc_dbg_free_dbg, que realizan un seguimiento de la asignación y desasignación de memoria. Esta asignación se produce únicamente en las compilaciones de depuración, que tienen _DEBUG. Las versiones de lanzamiento utilizan las funciones normales malloc y free .

Después de habilitar las funciones del montón de depuración mediante las instrucciones anteriores, realice una llamada a _CrtDumpMemoryLeaks antes de que un punto de salida de la aplicación muestre un informe de pérdida de memoria cuando se cierre la aplicación.

_CrtDumpMemoryLeaks();

Si la aplicación tiene varias salidas, no es necesario colocar _CrtDumpMemoryLeaks manualmente en cada punto de salida. Para que se produzca una llamada automática a _CrtDumpMemoryLeaks en cada punto de salida, coloque una llamada a _CrtSetDbgFlag al principio de la aplicación con los campos de bits que se muestran aquí:

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

De forma predeterminada, _CrtDumpMemoryLeaks genera el informe de pérdida de memoria en el panel de Depuración de la Ventana de salida . Si utiliza una biblioteca, esta podría hacer que se envíen los resultados a otra ubicación.

Puede usar _CrtSetReportMode para redirigir el informe a otra ubicación o volver a la ventana Salida como se muestra aquí:

_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG );

En el ejemplo siguiente se muestra una pérdida de memoria sencilla y se muestra información de pérdida de memoria mediante _CrtDumpMemoryLeaks();.

// debug_malloc.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_malloc.cpp
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>

int main()
{
    std::cout << "Hello World!\n";

    int* x = (int*)malloc(sizeof(int));

    *x = 7;

    printf("%d\n", *x);

    x = (int*)calloc(3, sizeof(int));
    x[0] = 7;
    x[1] = 77;
    x[2] = 777;

    printf("%d %d %d\n", x[0], x[1], x[2]);

    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); 
    _CrtDumpMemoryLeaks();
}

Interpretación del informe de fuga de memoria

Si la aplicación no define _CRTDBG_MAP_ALLOC, _CrtDumpMemoryLeaks muestra un informe de fuga de memoria con esta apariencia:

Detected memory leaks!
Dumping objects ->
{18} normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

Si la aplicación define _CRTDBG_MAP_ALLOC, el informe de fuga de memoria tiene el siguiente aspecto:

Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\leaktest\leaktest.cpp(20) : {18}
normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

El segundo informe muestra el nombre de archivo y el número de línea donde se asigna por primera vez la memoria perdida.

Tanto si define _CRTDBG_MAP_ALLOC como si no, el informe de fuga de memoria muestra lo siguiente:

  • El número de asignación de memoria, que es 18 en este ejemplo.
  • El tipo de bloque, normal en este ejemplo.
  • La ubicación de memoria hexadecimal, 0x00780E80 en este ejemplo.
  • El tamaño del bloque, 64 bytes en este ejemplo.
  • Los primeros 16 bytes de datos del bloque, en formato hexadecimal.

Los tipos de bloques de memoria son normal, cliente o CRT. Un bloque normal se compone de memoria ordinaria asignada por el programa. Un bloque cliente es un tipo de bloque de memoria especial utilizado por programas MFC para objetos que requieren un destructor. El operador new de MFC crea un bloque normal o un bloque cliente, según convenga, para el objeto que se está creando.

Un bloque CRT es asignado por la biblioteca CRT para su propio uso. La biblioteca de CRT controla la desasignación de estos bloques, por lo que los bloques de CRT no aparecerán en el informe de fuga de memoria a menos que haya problemas graves con la biblioteca de CRT.

Hay otros dos tipos de bloques de memoria que no aparecen nunca en los informes de pérdida de memoria. Un bloque libre es memoria que se ha liberado, por lo que, por definición, no se pierde. Un bloque omitido es memoria que se ha marcado explícitamente para excluirla del informe de fuga de memoria.

Las técnicas anteriores identifican fugas de memoria para la memoria asignada mediante la función estándar malloc de CRT. Sin embargo, si el programa asigna memoria mediante el C++ operador new, es posible que solo vea el nombre de archivo y el número de línea donde operator new llama a _malloc_dbg en el informe de fuga de memoria. Para crear un informe de fuga de memoria más útil, puede escribir una macro como la siguiente para informar de la línea que realizó la asignación:

#ifdef _DEBUG
    #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
    // Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
    // allocations to be of _CLIENT_BLOCK type
#else
    #define DBG_NEW new
#endif

Ahora, puede reemplazar el operador new mediante la macro DBG_NEW en el código. En las compilaciones de depuración, DBG_NEW usa una sobrecarga de global operator new que toma parámetros adicionales para el tipo de bloque, el archivo y el número de línea. La sobrecarga de new llama a _malloc_dbg para registrar la información adicional. Los informes de fuga de memoria muestran el nombre de archivo y el número de línea donde se asignaron los objetos perdidos. Las compilaciones de versión siguen usando el newpredeterminado. A continuación se muestra un ejemplo de la técnica:

// debug_new.cpp
// compile by using: cl /EHsc /W4 /D_DEBUG /MDd debug_new.cpp
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>

#ifdef _DEBUG
    #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
    // Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
    // allocations to be of _CLIENT_BLOCK type
#else
    #define DBG_NEW new
#endif

struct Pod {
    int x;
};

void main() {
    Pod* pPod = DBG_NEW Pod;
    pPod = DBG_NEW Pod; // Oops, leaked the original pPod!
    delete pPod;

    _CrtDumpMemoryLeaks();
}

Al ejecutar este código en el depurador de Visual Studio, la llamada a _CrtDumpMemoryLeaks genera un informe en la ventana Salida que tiene un aspecto similar a este:

Detected memory leaks!
Dumping objects ->
c:\users\username\documents\projects\debug_new\debug_new.cpp(20) : {75}
 normal block at 0x0098B8C8, 4 bytes long.
 Data: <    > CD CD CD CD
Object dump complete.

Esta salida informa de que la asignación perdida se encontraba en la línea 20 de debug_new. cpp.

Nota:

No se recomienda crear una macro de preprocesador denominada new ni cualquier otra palabra clave de lenguaje.

Establecimiento de puntos de interrupción en un número de asignación de memoria

El número de asignación de memoria le indica cuándo se asignó un bloque de pérdida de memoria. Un bloque con un número de asignación de memoria de 18, por ejemplo, es el decimoctavo bloque de memoria asignado durante la ejecución de la aplicación. El informe de CRT cuenta todas las asignaciones de bloques de memoria durante la ejecución, incluidas las asignaciones de la biblioteca de CRT y otras bibliotecas como MFC. Por lo tanto, el bloque de asignación de memoria número 18 probablemente no es el decimoctavo bloque de memoria asignado por el código.

Puede utilizar el número de asignación para establecer un punto de interrupción en la asignación de memoria.

Para definir un punto de interrupción de asignación de memoria mediante la ventana Inspección

  1. Establezca un punto de interrupción cerca del inicio de la aplicación e inicie la depuración.

  2. Cuando la aplicación se pausa en el punto de interrupción, abra una ventana Inspección seleccionando Depurar>Ventanas>Inspección 1 (o Inspección 2, Inspección 3 o Inspección 4).

  3. En la ventana Inspección, escriba _crtBreakAlloc en la columna Nombre.

    Si está utilizando la versión DLL de subprocesamiento múltiple de la biblioteca de CRT (la opción /MD), agregue el operador de contexto: {,,ucrtbased.dll}_crtBreakAlloc

    Asegúrese de que se cargan los símbolos de depuración. De lo contrario, _crtBreakAlloc se notifica como no identificado.

  4. Presione ENTRAR.

    El depurador evalúa la llamada y coloca el resultado en la columna Valor . Este valor es -1 si no ha establecido ningún punto de interrupción en las asignaciones de memoria.

  5. En la columna Valor, reemplace el valor por el número de asignación de la asignación de memoria donde quiere que el depurador realice la interrupción.

Después de establecer un punto de interrupción en un número de asignación de memoria, continúe con la depuración. Asegúrese de que la ejecución se produce en las mismas condiciones, de modo que no cambie el número de asignación de memoria. Cuando el programa se interrumpe en la asignación de memoria especificada, use la ventana Pila de llamadas y otras ventanas de depurador para determinar las condiciones en las que se asignó la memoria. A continuación, puede continuar la ejecución para observar qué le ocurre al objeto y determinar por qué no se desasigna correctamente.

También podría resultar útil definir un punto de interrupción de datos sobre el objeto. Para más información, vea Uso de puntos de interrupción.

También puede establecer puntos de interrupción de asignación de memoria en el código. Puede establecer:

_crtBreakAlloc = 18;

o:

_CrtSetBreakAlloc(18);

Comparación de estados de memoria

Otra técnica para localizar pérdidas de memoria requiere tomar instantáneas del estado de la memoria de la aplicación en determinados puntos clave. Para tomar una instantánea del estado de la memoria en un punto determinado de la aplicación, cree una estructura _CrtMemState y pásela a la función _CrtMemCheckpoint.

_CrtMemState s1;
_CrtMemCheckpoint( &s1 );

Esta función _CrtMemCheckpoint rellena la estructura con una instantánea del estado actual de la memoria.

Para generar el contenido de una estructura _CrtMemState, pase la estructura a la función _ CrtMemDumpStatistics:

_CrtMemDumpStatistics( &s1 );

_CrtMemDumpStatistics genera un volcado de memoria del estado de la memoria con esta apariencia:

0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
3071 bytes in 16 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 3071 bytes.
Total allocations: 3764 bytes.

Para determinar si se ha producido una pérdida de memoria en una sección de código, puede tomar instantáneas del estado de la memoria antes y después de la sección y, a continuación, utilizar _CrtMemDifference para comparar los dos estados:

_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )
   _CrtMemDumpStatistics( &s3 );

_CrtMemDifference compara los estados de la memoria s1 y s2, y devuelve un resultado en (s3) que es la diferencia entre s1 y s2.

Una técnica para buscar fugas de memoria empieza realizando llamadas a _CrtMemCheckpoint al principio y al final de la aplicación y, a continuación, utilizando _CrtMemDifference para comparar los resultados. Si _CrtMemDifference muestra una fuga de memoria, puede agregar más llamadas a _CrtMemCheckpoint para dividir el programa mediante una búsqueda binaria hasta que haya aislado el origen de la fuga.

Falsos positivos

_CrtDumpMemoryLeaks puede proporcionar indicaciones falsas de fugas de memoria si una biblioteca marca las asignaciones internas como bloques normales en lugar de bloques de CRT o bloques de cliente. En tal caso, _CrtDumpMemoryLeaks no puede distinguir entre las asignaciones del usuario y las asignaciones de la biblioteca internas. Si los destructores globales de las asignaciones de biblioteca se ejecutan después del punto donde se llamó a _CrtDumpMemoryLeaks, cada asignación interna de la biblioteca se notifica como pérdida de memoria. Las versiones de la Biblioteca de plantillas estándar anteriores a Visual Studio .NET pueden hacer que _CrtDumpMemoryLeaks notifique estos falsos positivos.

Consulte también