Detalles del montón de depuración de CRT

El montón de depuración de CRT y las funciones relacionadas proporcionan muchas maneras de realizar un seguimiento y depurar problemas de administración de memoria en el código. Puede usarlo para buscar saturaciones del búfer y realizar un seguimiento de las asignaciones de memoria y el estado de memoria. También admite la creación de sus propias funciones de asignación de depuración para sus necesidades de aplicación únicas.

Buscar saturaciones de búfer mediante el montón de depuración

Dos de los problemas más comunes e intractables que encuentran los programadores están sobrescribiendo el final de un búfer asignado y las pérdidas de memoria (no se pueden liberar asignaciones después de que ya no sean necesarias). El montón de depuración proporciona herramientas eficaces para solucionar este tipo de problemas de asignación de memoria.

Las versiones de depuración de las funciones del montón llaman a las versiones base o estándar utilizadas en las versiones de lanzamiento. Cuando se solicita un bloque de memoria, el administrador del montón de depuración asigna desde el montón base un bloque de memoria ligeramente mayor del solicitado y devuelve un puntero a la parte de ese bloque. Por ejemplo, suponga que la aplicación contiene la llamada: malloc( 10 ). En una compilación de versión, malloc llamaría a la rutina de asignación del montón base que solicitaba una asignación de 10 bytes. Sin embargo, en una compilación de depuración llamaría malloc a , que luego llamaría _malloc_dbga la rutina de asignación del montón base que solicitaba una asignación de 10 bytes más aproximadamente 36 bytes de memoria adicional. Todos los bloques de memoria del montón de depuración se encuentran conectados en una lista simplemente vinculada, ordenados según el momento en que fueron asignados.

La memoria adicional asignada por las rutinas del montón de depuración se usa para la información de contabilidad. Tiene punteros que vinculan bloques de memoria de depuración juntos y pequeños búferes en cualquiera de los lados de los datos para capturar sobrescrituras de la región asignada.

Actualmente, la estructura de encabezado de bloque usada para almacenar la información de contabilidad del montón de depuración se declara en el <crtdbg.h> encabezado y se define en el <debug_heap.cpp> archivo de origen de CRT. Conceptualmente, es similar a esta estructura:

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    _CrtMemBlockHeader* _block_header_next;
// Pointer to the block allocated just after this one:
    _CrtMemBlockHeader* _block_header_prev;
    char const*         _file_name;
    int                 _line_number;

    int                 _block_use;      // Type of block
    size_t              _data_size;      // Size of user block

    long                _request_number; // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char       _gap[no_mans_land_size];

    // Followed by:
    // unsigned char    _data[_data_size];
    // unsigned char    _another_gap[no_mans_land_size];
} _CrtMemBlockHeader;

Los no_mans_land búferes de cualquier lado del área de datos de usuario del bloque tienen actualmente un tamaño de 4 bytes y se rellenan con un valor de byte conocido usado por las rutinas del montón de depuración para comprobar que no se han sobrescrito los límites del bloque de memoria del usuario. El montón de depuración también rellena nuevos bloques de memoria con un valor conocido. Si decide mantener los bloques liberados en la lista vinculada del montón, estos bloques liberados también se rellenan con un valor conocido. Comúnmente, los valores de byte utilizados son:

no_mans_land (0xFD)
Los búferes de "no_mans_land" en cualquiera de los lados de la memoria usada por una aplicación se rellenan actualmente con 0xFD.

Bloques liberados (0xDD)
Los bloques liberados que permanecían sin utilizar en la lista vinculada del montón de depuración, cuando se establece el marcador _CRTDBG_DELAY_FREE_MEM_DF, se rellenan con 0xDD.

Nuevos objetos (0xCD)
Los nuevos objetos se rellenan con 0xCD cuando se asignan.

Tipos de bloques en el montón de depuración

Cada bloque de memoria del montón de depuración se asigna a uno de entre cinco tipos de asignación. Estos tipos reciben un seguimiento y se informa de ellos de forma diferente en cuanto a detección de pérdidas e informe de estados. Para especificar el tipo de un bloque, puede asignarlo mediante una llamada directa a una de las funciones de asignación del montón de depuración, como _malloc_dbg. Los cinco tipos de bloques de memoria del montón de depuración (establecido en el nBlockUse miembro de la _CrtMemBlockHeader estructura) son los siguientes:

_NORMAL_BLOCK
Una llamada a malloc o calloc crea un bloque Normal. Si solo tiene previsto usar bloques normales y no necesita bloques de cliente, puede definir _CRTDBG_MAP_ALLOC. _CRTDBG_MAP_ALLOC hace que todas las llamadas de asignación del montón se asignen a sus equivalentes de depuración en compilaciones de depuración. Permite el almacenamiento de información de nombre de archivo y número de línea sobre cada llamada de asignación en el encabezado de bloque correspondiente.

_CRT_BLOCK
Los bloques de memoria asignados internamente por muchas de las funciones de la biblioteca en tiempo de ejecución se marcan como bloques CRT, de modo que se puedan controlar por separado. Como resultado, la detección de fugas y otras operaciones pueden permanecer sin verse afectadas por ellas. En una asignación de memoria, nunca se debe asignar, reasignar o liberar ningún bloque de tipo CRT.

_CLIENT_BLOCK
Una aplicación puede mantener un seguimiento especial de un determinado grupo de asignaciones de memoria, a efectos de depuración, si las asigna como este tipo de bloque de memoria y utiliza llamadas explícitas para las funciones del montón de depuración. MFC, por ejemplo, asigna todos los CObject objetos como bloques de cliente; otras aplicaciones pueden mantener objetos de memoria diferentes en bloques de cliente. Asimismo, se pueden especificar subtipos de bloques Cliente para conseguir un mayor detalle en el seguimiento. Si desea especificar subtipos de bloques Cliente, desplace el número 16 bits a la izquierda y súmelo, mediante la operación lógica OR, con _CLIENT_BLOCK. Por ejemplo:

#define MYSUBTYPE 4
freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));

Se puede instalar una función de enlace proporcionada por el cliente para volcar los objetos almacenados en bloques de cliente mediante _CrtSetDumpClienty, a continuación, se llamará cada vez que una función de depuración volcará un bloque de cliente. Además, _CrtDoForAllClientObjects se puede usar para llamar a una función determinada proporcionada por la aplicación para cada bloque de cliente del montón de depuración.

_FREE_BLOCK
Normalmente, los bloques que se han liberado se quitan de la lista. Para comprobar que la memoria libre no está escrita en o para simular condiciones de memoria bajas, puede mantener los bloques liberados en la lista vinculada, marcados como Gratis y rellenados con un valor de byte conocido (actualmente 0xDD).

_IGNORE_BLOCK
Es posible desactivar las operaciones del montón de depuración durante algún intervalo. En este período, los bloques de memoria se mantienen en la lista, pero se marcan como bloques Omitir.

Para determinar el tipo y el subtipo de un bloque determinado, use la función _CrtReportBlockType y las macros _BLOCK_TYPE y _BLOCK_SUBTYPE. Las macros se definen de <crtdbg.h> la siguiente manera:

#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)

Comprobar la integridad y pérdidas de memoria del montón

El acceso a muchas de las características del montón de depuración se debe realizar desde dentro del código. En la sección siguiente se describen algunas características y cómo utilizarlas.

_CrtCheckMemory
Puede usar una llamada a _CrtCheckMemory, por ejemplo, para comprobar la integridad del montón en cualquier momento. Esta función inspecciona cada bloque de memoria del montón. Comprueba que la información del encabezado del bloque de memoria es válida y confirma que los búferes no se han modificado.

_CrtSetDbgFlag
Puede controlar cómo el montón de depuración realiza un seguimiento de las asignaciones mediante una marca interna, , _crtDbgFlagque se puede leer y establecer mediante la _CrtSetDbgFlag función . Cambiando este marcador, se puede indicar al montón de depuración que compruebe si existen pérdidas de memoria cuando el programa termina, e informa de las pérdidas detectadas. Del mismo modo, puede indicar al montón que deje bloques de memoria libres en la lista vinculada para simular situaciones de poca memoria. Cuando se comprueba el montón, estos bloques liberados se inspeccionan en su totalidad para asegurarse de que no se han molestado.

La _crtDbgFlag marca contiene los siguientes campos de bits:

Campo de bit Valor predeterminado Descripción
_CRTDBG_ALLOC_MEM_DF Activado Activa la asignación para depuración. Cuando este bit está desactivado, las asignaciones permanecen encadenadas, pero su tipo de bloque es _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Desconectado Impide la liberación real de memoria, como en la simulación del estado de memoria escasa. Cuando este bit está activado, los bloques liberados se mantienen en la lista vinculada del montón de depuración, pero se marcan como _FREE_BLOCK y se rellenan con un valor de byte especial.
_CRTDBG_CHECK_ALWAYS_DF Desconectado Hace _CrtCheckMemory que se llame a en cada asignación y desasignación. La ejecución es más lenta, pero detecta errores rápidamente.
_CRTDBG_CHECK_CRT_DF Desconectado Hace que los bloques marcados como tipo _CRT_BLOCK se incluyan en las operaciones de detección de fugas y diferencia de estado. Cuando este bit está desactivado, la memoria utilizada internamente por la biblioteca en tiempo de ejecución se omite durante esas operaciones.
_CRTDBG_LEAK_CHECK_DF Desconectado Hace que la comprobación de fugas se realice en la salida del programa a través de una llamada a _CrtDumpMemoryLeaks. Si la aplicación no consigue liberar toda la memoria asignada, se genera un informe de error.

Configurar el montón de depuración

Todas las llamadas a funciones del montón, como malloc, free, calloc, realloc, new y delete, se resuelven en versiones de depuración de esas funciones que operan sobre el montón de depuración. Cuando se libera un bloque de memoria, el montón de depuración comprueba automáticamente la integridad de los búferes situados a cada lado del área asignada y emite un informe de error en caso de sobrescritura.

Para utilizar el montón de depuración

  • Vincule la compilación de depuración de la aplicación con una versión de depuración de la biblioteca en tiempo de ejecución de C.

Para cambiar uno o varios _crtDbgFlag campos de bits y crear un nuevo estado para la marca

  1. Llame a _CrtSetDbgFlag con el parámetro newFlag definido como _CRTDBG_REPORT_FLAG (para obtener el estado actual de _crtDbgFlag) y almacene el valor devuelto en una variable temporal.

  2. Active los bits mediante un operador bit a bit | ("o") en la variable temporal con las máscaras de bits correspondientes (representadas en el código de aplicación por constantes de manifiesto).

  3. Desactive los demás bits mediante un operador bit a bit & ("and") en la variable con un operador bit a bit ~ ("not" o complemento) de las máscaras de bits adecuadas.

  4. Llame a _CrtSetDbgFlag con el parámetro newFlag establecido en el valor almacenado en la variable temporal para crear el nuevo estado de _crtDbgFlag.

    Por ejemplo, las siguientes líneas de código habilitan la detección automática de fugas y deshabilitan las comprobaciones de los bloques de tipo _CRT_BLOCK:

    // Get current flag
    int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
    
    // Turn on leak-checking bit.
    tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
    
    // Turn off CRT block checking bit.
    tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;
    
    // Set flag to the new value.
    _CrtSetDbgFlag( tmpFlag );
    

newAsignaciones , deletey _CLIENT_BLOCK en el montón de depuración de C++

Las versiones de depuración de la biblioteca en tiempo de ejecución de C contienen versiones de depuración de los operadores new y delete de C++. Si usa el tipo de asignación _CLIENT_BLOCK, debe llamar a la versión de depuración del operador new directamente o crear macros que reemplacen al operador new en modo de depuración, como se muestra en el siguiente ejemplo:

/* MyDbgNew.h
 Defines global operator new to allocate from
 client blocks
*/

#ifdef _DEBUG
   #define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
   #define DEBUG_CLIENTBLOCK
#endif // _DEBUG

/* MyApp.cpp
        Use a default workspace for a Console Application to
 *      build a Debug version of this code
*/

#include "crtdbg.h"
#include "mydbgnew.h"

#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif

int main( )   {
    char *p1;
    p1 =  new char[40];
    _CrtMemDumpAllObjectsSince( NULL );
}

La versión de depuración del operador delete funciona con todos los tipos de bloques y no requiere realizar cambios en el programa cuando se compila una versión de lanzamiento.

Funciones de informes de estado del montón

Para capturar una instantánea de resumen del estado del montón en un momento dado, use la _CrtMemState estructura definida en <crtdbg.h>:

typedef struct _CrtMemState
{
    // Pointer to the most recently allocated block:
    struct _CrtMemBlockHeader * pBlockHeader;
    // A counter for each of the 5 types of block:
    size_t lCounts[_MAX_BLOCKS];
    // Total bytes allocated in each block type:
    size_t lSizes[_MAX_BLOCKS];
    // The most bytes allocated at a time up to now:
    size_t lHighWaterCount;
    // The total bytes allocated at present:
    size_t lTotalCount;
} _CrtMemState;

Esta estructura guarda un puntero al primer bloque (el más recientemente asignado) en la lista vinculada del montón de depuración. A continuación, en dos matrices, registra cuántos de cada tipo de bloque de memoria (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCKetc.) se encuentran en la lista y el número de bytes asignados en cada tipo de bloque. Por último, registra el mayor número de bytes asignado en el montón de manera global hasta ese punto y el número de bytes asignado actualmente.

Otras funciones de informes de CRT

Las siguientes funciones informan del estado y el contenido del montón, y utilizan la información para ayudar a detectar pérdidas de memoria y otros problemas.

Función Descripción
_CrtMemCheckpoint Guarda una instantánea del montón en una _CrtMemState estructura proporcionada por la aplicación.
_CrtMemDifference Compara dos estructuras de estados de memoria, guarda la diferencia entre ellas en una tercera estructura de estado y devuelve TRUE si los dos estados son diferentes.
_CrtMemDumpStatistics Volca una estructura determinada _CrtMemState . La estructura puede contener una instantánea del estado del montón de depuración en un momento dado, o la diferencia entre dos instantáneas.
_CrtMemDumpAllObjectsSince Realiza un volcado de memoria de información de todos los objetos asignados desde que se tomó una determinada instantánea del montón o desde el inicio de la ejecución. Cada vez que volca un _CLIENT_BLOCK bloque, llama a una función de enlace proporcionada por la aplicación, si se ha instalado una mediante _CrtSetDumpClient.
_CrtDumpMemoryLeaks Determina si se produjo alguna pérdida de memoria desde el inicio de la ejecución del programa; en ese caso, realiza un volcado de memoria de todos los objetos asignados. Cada vez que _CrtDumpMemoryLeaks volca un _CLIENT_BLOCK bloque, llama a una función de enlace proporcionada por la aplicación, si se ha instalado una mediante _CrtSetDumpClient.

Seguimiento de solicitudes de asignación de montón

Conocer el nombre del archivo de origen y el número de línea de una macro de aserción o informe suele ser útil para localizar la causa de un problema. Lo mismo no es probable que sea cierto en las funciones de asignación del montón. Aunque puede insertar macros en muchos puntos adecuados en el árbol lógico de una aplicación, una asignación se suele enterrar en una función a la que se llama desde muchos lugares diferentes en muchas ocasiones diferentes. La pregunta no es qué línea de código hizo una asignación incorrecta. En su lugar, es cuál de las miles de asignaciones realizadas por esa línea de código era incorrecta y por qué.

Números de solicitud de asignación únicos y _crtBreakAlloc

Hay una manera sencilla de identificar la llamada de asignación de montón específica que se ha producido mal. Aprovecha el número de solicitud de asignación único asociado a cada bloque del montón de depuración. Cuando alguna de las funciones de volcado de memoria proporciona información sobre un bloque, este número de solicitud de asignación aparece encerrado entre llaves (por ejemplo, "{36}").

Una vez que sepa el número de solicitud de asignación de un bloque asignado incorrectamente, puede pasar este número para _CrtSetBreakAlloc crear un punto de interrupción. La ejecución se interrumpe justo antes de asignar el bloque y se puede realizar un seguimiento hacia atrás para determinar qué rutina fue la responsable de la llamada errónea. Para evitar la recompilación, puede lograr lo mismo en el depurador estableciendo _crtBreakAlloc en el número de solicitud de asignación que le interesa.

Creación de versiones de depuración de las rutinas de asignación

Un enfoque más complejo es crear versiones de depuración de sus propias rutinas de asignación, comparables a las versiones de las _dbgfunciones de asignación del montón. A continuación, puede pasar argumentos de número de línea y de archivo de origen a las rutinas de asignación del montón subyacentes, y podrá ver inmediatamente dónde se originó una asignación incorrecta.

Por ejemplo, supongamos que la aplicación contiene una rutina usada habitualmente similar a la del ejemplo siguiente:

int addNewRecord(struct RecStruct * prevRecord,
                 int recType, int recAccess)
{
    // ...code omitted through actual allocation...
    if ((newRec = malloc(recSize)) == NULL)
    // ... rest of routine omitted too ...
}

En un archivo de encabezado, podría agregar código como el ejemplo siguiente:

#ifdef _DEBUG
#define  addNewRecord(p, t, a) \
            addNewRecord(p, t, a, __FILE__, __LINE__)
#endif

A continuación, se podría cambiar la asignación en la rutina de creación de registros del siguiente modo:

int addNewRecord(struct RecStruct *prevRecord,
                int recType, int recAccess
#ifdef _DEBUG
               , const char *srcFile, int srcLine
#endif
    )
{
    /* ... code omitted through actual allocation ... */
    if ((newRec = _malloc_dbg(recSize, _NORMAL_BLOCK,
            srcFile, scrLine)) == NULL)
    /* ... rest of routine omitted too ... */
}

Ahora, el nombre del archivo de código fuente y el número de línea donde se llamó a addNewRecord se almacenarán en cada bloque resultante asignado en el montón de depuración y se informará de ellos al examinar el bloque.

Consulte también

Depuración de código nativo