Partager via


Détails du tas de débogage CRT

Le tas de débogage CRT et les fonctions associées offrent de nombreuses façons de suivre et de déboguer les problèmes de gestion de la mémoire dans votre code. Vous pouvez l’utiliser pour rechercher des dépassements de mémoire tampon et pour suivre et signaler les allocations de mémoire et l’état de la mémoire. Il prend également en charge la création de vos propres fonctions d’allocation de débogage pour vos besoins d’application uniques.

Rechercher les dépassements de mémoire tampon avec le tas de débogage

Deux des problèmes les plus courants et inductibles rencontrés par les programmeurs remplacent la fin d’une mémoire tampon allouée et des fuites de mémoire (échec des allocations gratuites après qu’ils ne sont plus nécessaires). Le tas de débogage fournit des outils puissants pour résoudre les problèmes d'allocation de mémoire de ce type.

Les versions Debug des fonctions du tas appellent les versions standard ou de base utilisées dans les versions Release. Lorsque vous demandez un bloc de mémoire, le gestionnaire de tas de débogage alloue à partir du tas de base un bloc de mémoire légèrement plus grand que vous l’avez demandé et retourne un pointeur vers votre partie de ce bloc. Par exemple, supposons que votre application contient l'appel : malloc( 10 ). Dans une build Release, malloc appelez la routine d’allocation de tas de base demandant une allocation de 10 octets. Dans une build de débogage, cependant, malloc appellerait _malloc_dbg, qui appellerait ensuite la routine d’allocation de tas de base demandant une allocation de 10 octets plus environ 36 octets de mémoire supplémentaire. Tous les blocs de mémoire résultants dans le tas de débogage sont connectés dans une seule liste liée, ordonnée en fonction du moment où ils ont été alloués.

La mémoire supplémentaire allouée par les routines de tas de débogage est utilisée pour les informations de comptabilité. Il comporte des pointeurs qui relient des blocs de mémoire de débogage ensemble, ainsi que de petites mémoires tampons sur les deux côtés de vos données pour intercepter les remplacements de la région allouée.

Actuellement, la structure d’en-tête de bloc utilisée pour stocker les informations de comptabilité du tas de débogage est déclarée dans l’en-tête <crtdbg.h> et définie dans le <debug_heap.cpp> fichier source CRT. Conceptuellement, il est similaire à cette structure :

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;

Les no_mans_land mémoires tampons de chaque côté de la zone de données utilisateur du bloc sont actuellement de 4 octets de taille et sont remplies d’une valeur d’octet connue utilisée par les routines de tas de débogage pour vérifier que les limites du bloc de mémoire de l’utilisateur n’ont pas été remplacées. Le tas de débogage remplit également les nouveaux blocs de mémoire avec une valeur connue. Si vous choisissez de conserver des blocs libérés dans la liste liée du tas, ces blocs libérés sont également remplis avec une valeur connue. Actuellement, les valeurs d'octets réelles utilisées sont les suivantes :

no_mans_land (0xFD)
Les mémoires tampons « no_mans_land » de chaque côté de la mémoire utilisée par une application sont actuellement remplies de 0xFD.

Blocs libérés (0xDD)
Les blocs libérés restés inutilisés dans la liste liée du tas de débogage lorsque l'indicateur _CRTDBG_DELAY_FREE_MEM_DF est défini contiennent actuellement 0xDD.

Nouveaux objets (0xCD)
Les nouveaux objets sont remplis de 0xCD lorsqu’ils sont alloués.

Types de bloc sur le tas de débogage

Chaque bloc de mémoire dans le tas de débogage est assigné à l'un des cinq types d'allocations. Ces types sont suivis et reportés différemment pour la détection des fuites et la création de rapports d'état. Vous pouvez spécifier le type d’un bloc en l’allouant à l’aide d’un appel direct à l’une des fonctions d’allocation de tas de débogage telles que _malloc_dbg. Les cinq types de blocs de mémoire dans le tas de débogage (définis dans le nBlockUse membre de la _CrtMemBlockHeader structure) sont les suivants :

_NORMAL_BLOCK
Appel ou création d’un malloc calloc bloc normal. Si vous envisagez d’utiliser uniquement des blocs Normaux et que vous n’avez pas besoin de blocs client, vous pouvez définir _CRTDBG_MAP_ALLOC. _CRTDBG_MAP_ALLOC entraîne le mappage de tous les appels d’allocation de tas à leurs équivalents de débogage dans les builds Debug. Il autorise le stockage des informations de nom de fichier et de numéro de ligne sur chaque appel d’allocation dans l’en-tête de bloc correspondant.

_CRT_BLOCK
Les blocs de mémoire alloués en interne par de nombreuses fonctions de la bibliothèque Runtime sont marqués comme des blocs CRT pour pouvoir être traités séparément. Par conséquent, la détection des fuites et d’autres opérations peuvent rester inchangées. Une allocation ne doit jamais allouer, réallouer ou libérer un bloc de type CRT.

_CLIENT_BLOCK
Pour les besoins du débogage, une application peut effectuer un suivi spécial d'un groupe donné d'allocations en leur associant ce type de bloc de mémoire, avec des appels explicites aux fonctions du tas de débogage. MFC, par exemple, alloue tous les CObject objets en tant que blocs client ; d’autres applications peuvent conserver différents objets de mémoire dans les blocs client. Il est également possible de spécifier des sous-types de bloc Client afin d'augmenter la granularité du suivi. Pour spécifier des sous-types de bloc Client, décalez le nombre de gauche de 16 bits et faites une réunion logique (OR) avec _CLIENT_BLOCK. Par exemple :

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

Une fonction de hook fournie par le client pour vider les objets stockés dans les blocs client peut être installée à l’aide _CrtSetDumpClientde , puis est appelée chaque fois qu’un bloc client est vidé par une fonction de débogage. _CrtDoForAllClientObjects Il est également possible d’appeler une fonction donnée fournie par l’application pour chaque bloc client dans le tas de débogage.

_FREE_BLOCK
Normalement, les blocs qui sont libérés sont supprimés de la liste. Pour vérifier que la mémoire libérée n’est pas écrite ou si vous simulez des conditions de mémoire faible, vous pouvez conserver des blocs libérés dans la liste liée, marquée comme Libre et remplie d’une valeur d’octet connue (actuellement 0xDD).

_IGNORE_BLOCK
Il est possible de désactiver les opérations de tas de débogage pendant un certain intervalle. Pendant cette période, les blocs de mémoire sont conservés dans la liste, mais marqués en tant que blocs Ignore.

Pour déterminer le type et le sous-type d’un bloc donné, utilisez la fonction _CrtReportBlockType et les macros _BLOCK_TYPE et _BLOCK_SUBTYPE. Les macros sont définies comme <crtdbg.h> suit :

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

Contrôler l'intégrité et les fuites de mémoire de tas

L'accès à de nombreuses fonctionnalités du tas de débogage doit s'effectuer à partir de votre code. La section suivante décrit certaines fonctionnalités et la façon de les utiliser.

_CrtCheckMemory
Vous pouvez utiliser un appel à _CrtCheckMemory, par exemple, pour vérifier l’intégrité du tas à tout moment. Cette fonction inspecte chaque bloc de mémoire dans le tas. Il vérifie que les informations d’en-tête du bloc de mémoire sont valides et confirme que les mémoires tampons n’ont pas été modifiées.

_CrtSetDbgFlag
Vous pouvez contrôler la façon dont le tas de débogage effectue le suivi des allocations à l’aide d’un indicateur interne, _crtDbgFlagqui peut être lu et défini à l’aide de la _CrtSetDbgFlag fonction. Vous pouvez, en modifiant cet indicateur, ordonner au tas de débogage de rechercher les fuites de mémoire lorsque le programme s'arrête et de signaler les fuites détectées. De même, vous pouvez indiquer au tas de laisser des blocs de mémoire libérés dans la liste liée pour simuler des situations de mémoire faible. Lorsque le tas est vérifié, ces blocs libérés sont inspectés dans leur intégralité pour s’assurer qu’ils n’ont pas été perturbés.

L’indicateur _crtDbgFlag contient les champs de bits suivants :

Champ de bits Valeur par défaut Description
_CRTDBG_ALLOC_MEM_DF Activé Active l'allocation de débogage. Lorsque ce bit est désactivé, les allocations restent chaînées ensemble, mais leur type de bloc est _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Off Interdit la libération réelle de la mémoire, comme pour la simulation de conditions de mémoire insuffisante. Lorsque ce bit est activé, les blocs libérés sont conservés dans la liste liée du tas de débogage, mais sont marqués comme _FREE_BLOCK et remplis avec une valeur d’octet spéciale.
_CRTDBG_CHECK_ALWAYS_DF Désactivé Causes _CrtCheckMemory à appeler à chaque allocation et désallocation. L’exécution est plus lente, mais elle intercepte rapidement les erreurs.
_CRTDBG_CHECK_CRT_DF Désactivé Provoque l’inclusion de blocs marqués comme _CRT_BLOCK type dans les opérations de détection de fuite et de différence d’état. Lorsque ce bit est à 0, la mémoire utilisée en interne par la bibliothèque Runtime est ignorée pendant ces opérations.
_CRTDBG_LEAK_CHECK_DF Désactivé Provoque l’exécution de la vérification des fuites à la sortie du programme via un appel à _CrtDumpMemoryLeaks. Un rapport d'erreurs est généré si l'application n'a pas pu libérer toute la mémoire qu'elle a allouée.

Configurer le tas de débogage

Tous les appels aux fonctions du tas, telles que malloc, free, calloc, realloc, new et delete, sont traduits dans les versions Debug de ces fonctions qui opèrent dans le tas de débogage. Lorsque vous libérez un bloc de mémoire, le tas de débogage vérifie automatiquement l'intégrité des mémoires tampons de chaque côté de votre zone allouée et envoie un rapport d'erreur si un remplacement a eu lieu.

Pour utiliser le tas de débogage

  • Liez la build de débogage de votre application à une version de débogage de la bibliothèque runtime C.

Pour modifier un ou plusieurs _crtDbgFlag champs de bits et créer un état pour l’indicateur

  1. Appelez _CrtSetDbgFlag alors que le paramètre newFlag a la valeur _CRTDBG_REPORT_FLAG (pour obtenir l'état actuel de _crtDbgFlag) et stockez la valeur retournée dans une variable temporaire.

  2. Activez tous les bits à l’aide d’un opérateur au niveau | du bit (« ou ») sur la variable temporaire avec les masques de bits correspondants (représentés dans le code de l’application par constantes manifestes).

  3. Désactivez les autres bits à l’aide d’un opérateur au niveau & du bit (« and ») sur la variable avec un opérateur au niveau ~ du bit (« non » ou un complément) des masques de bits appropriés.

  4. Appelez _CrtSetDbgFlag alors que le paramètre newFlag a la valeur stockée dans la variable temporaire afin de créer l'état de _crtDbgFlag.

    Par exemple, les lignes de code suivantes activent la détection automatique des fuites et désactivent les vérifications des blocs de type _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 );
    

new, deleteet _CLIENT_BLOCK allocations dans le tas de débogage C++

Les versions de débogage de la bibliothèque Runtime C contiennent les versions de débogage des opérateurs C++ new et delete. Si vous utilisez le type d'allocation _CLIENT_BLOCK, vous devez appeler la version Debug de l'opérateur new directement ou créer des macros qui remplacent l'opérateur new en mode debug, comme le montre l'exemple suivant :

/* 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 version Debug de l’opérateur delete fonctionne avec tous les types de bloc et ne nécessite aucune modification dans votre programme lorsque vous compilez une version Release.

Fonctions de création de rapports d’état de tas

Pour capturer un instantané récapitulative de l’état du tas à un moment donné, utilisez la _CrtMemState structure définie dans <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;

Cette structure enregistre un pointeur vers le premier bloc (celui qui a été alloué en dernier) dans la liste liée du tas de débogage. Ensuite, dans deux tableaux, il enregistre le nombre de chaque type de bloc de mémoire (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCKet ainsi de suite) dans la liste et le nombre d’octets alloués dans chaque type de bloc. Enfin, elle enregistre le plus grand nombre d'octets alloués globalement dans le tas jusqu'à ce point et le nombre d'octets actuellement alloués.

Autres fonctions de création de rapports CRT

Les fonctions suivantes reportent l'état et le contenu du tas, et utilisent les informations pour faciliter la détection des fuites de mémoire et des autres problèmes.

Fonction Description
_CrtMemCheckpoint Enregistre un instantané du tas dans une _CrtMemState structure fournie par l’application.
_CrtMemDifference Compare deux structures d'état de mémoire, enregistre la différence entre ces dernières dans une troisième structure d'état et retourne TRUE si les deux états sont différents.
_CrtMemDumpStatistics Vide une structure donnée _CrtMemState . La structure peut contenir un instantané de l'état du tas de débogage à un moment donné ou la différence entre deux instantanés.
_CrtMemDumpAllObjectsSince Fait un dump des informations sur tous les objets alloués depuis la capture d'un instantané donné du tas ou le début de l'exécution. Chaque fois qu’il vide un _CLIENT_BLOCK bloc, il appelle une fonction de hook fournie par l’application, si une fonction a été installée à l’aide _CrtSetDumpClientde .
_CrtDumpMemoryLeaks Détermine si des fuites de mémoire se sont produites depuis le début de l'exécution du programme et, dans ce cas, fait un dump de tous les objets alloués. Chaque fois _CrtDumpMemoryLeaks qu’un _CLIENT_BLOCK bloc est vide, il appelle une fonction de hook fournie par l’application, si une fonction a été installée à l’aide _CrtSetDumpClientde .

Suivre les demandes d’allocation de tas

Connaître le nom de fichier source et le numéro de ligne d’une macro d’assertion ou de création de rapports est souvent utile pour localiser la cause d’un problème. La même chose n’est pas aussi susceptible d’être vraie des fonctions d’allocation de tas. Bien que vous puissiez insérer des macros à de nombreux points appropriés dans l’arborescence logique d’une application, une allocation est souvent enterrée dans une fonction appelée à partir de nombreux endroits différents à plusieurs moments différents. La question n’est pas ce que la ligne de code a fait une mauvaise allocation. Au lieu de cela, il s’agit de l’une des milliers d’allocations effectuées par cette ligne de code, et pourquoi.

Numéros de demande d’allocation unique et _crtBreakAlloc

Il existe un moyen simple d’identifier l’appel d’allocation de tas spécifique qui s’est mal passé. Il tire parti du numéro de demande d’allocation unique associé à chaque bloc dans le tas de débogage. Quand les informations sur un bloc sont reportées par l’une des fonctions de dump, ce numéro de demande d’allocation est placé entre accolades (par exemple « {36} »).

Une fois que vous connaissez le numéro de demande d’allocation d’un bloc alloué de manière incorrecte, vous pouvez passer ce numéro pour _CrtSetBreakAlloc créer un point d’arrêt. L'exécution sera interrompue juste avant l'allocation du bloc et vous pourrez effectuer des recherches rétroactives pour déterminer quelle routine est à l'origine de l'appel incorrect. Pour éviter la recompilation, vous pouvez accomplir la même chose dans le débogueur en définissant _crtBreakAlloc le numéro de demande d’allocation qui vous intéresse.

Création de versions de débogage de vos routines d’allocation

Une approche plus complexe consiste à créer des versions de débogage de vos propres routines d’allocation, comparables aux _dbg versions des fonctions d’allocation de tas. Vous pouvez ensuite passer des arguments de numéro de ligne et de fichier source aux routines d’allocation de tas sous-jacentes, et vous serez immédiatement en mesure de voir où provient une allocation incorrecte.

Par exemple, supposons que votre application contient une routine couramment utilisée semblable à l’exemple suivant :

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

Dans un fichier d’en-tête, vous pouvez ajouter du code tel que l’exemple suivant :

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

Vous pourriez ensuite changer l'allocation dans votre routine de création d'enregistrements de la façon suivante :

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 ... */
}

Dorénavant, le nom du fichier source et le numéro de ligne où addNewRecord a été appelé seront stockés à l'intérieur de chaque bloc résultant alloué dans le tas de débogage et seront reportés lors de l'examen de ce bloc.

Voir aussi

Débogage du code natif