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 _CrtSetDumpClient
de , 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, _crtDbgFlag
qui 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
Appelez
_CrtSetDbgFlag
alors que le paramètrenewFlag
a la valeur_CRTDBG_REPORT_FLAG
(pour obtenir l'état actuel de_crtDbgFlag
) et stockez la valeur retournée dans une variable temporaire.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).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.Appelez
_CrtSetDbgFlag
alors que le paramètrenewFlag
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
, delete
et _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_BLOCK
et 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 _CrtSetDumpClient de . |
_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 _CrtSetDumpClient de . |
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.