Rechercher les fuites de mémoire avec la bibliothèque CRT

Les fuites de mémoire font partie des bogues les plus subtils et les plus difficiles à détecter dans les applications C/C++. Les fuites de mémoire résultent de l’incapacité à libérer correctement de la mémoire précédemment allouée. Au début, vous pourrez remarquer une petite fuite de mémoire, mais au fil du temps, elle peut provoquer des problèmes allant d’une perte de performances à des blocages d’applications, lorsque la mémoire devient insuffisante. Une application connaissant une fuite de mémoire qui utiliserait toute la mémoire disponible pourrait entraîner le blocage d’autres applications, provoquant le doute quant à l’application réellement responsable du blocage. Même les fuites de mémoire sans incidence peuvent indiquer d’autres problèmes devant être corrigés.

Le débogueur Visual Studio et la bibliothèque runtime C (CRT) peuvent vous aider à détecter et à identifier les fuites de mémoire.

Activer la détection des fuites de mémoire

Les principaux outils de détection des fuites de mémoire sont le débogueur C/C++ et les fonctions de tas de débogage CRT.

Pour activer toutes les fonctions du tas de débogage, incluez les instructions suivantes dans votre programme C++, dans l’ordre suivant :

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

L'instruction #define mappe une version de base des fonctions de tas CRT vers la version Debug correspondante. Si vous omettez l’instruction #define, le dump de fuite de mémoire sera moins détaillé.

L’inclusion de crtdbg.h mappe les fonctions et free les fonctions à leurs versions de débogage, _malloc_dbg et _free_dbg, qui effectuent le malloc suivi de l’allocation de mémoire et de la désallocation. Ce mappage se produit uniquement dans les versions Debug avec _DEBUG. Les versions Release utilisent les fonctions malloc et free ordinaires.

Une fois que vous avez activé les fonctions de tas de débogage à l’aide des instructions précédentes, placez un appel avant _CrtDumpMemoryLeaks qu’un point de sortie d’application affiche un rapport de fuite de mémoire lorsque l’application quitte.

_CrtDumpMemoryLeaks();

Si votre application a plusieurs sorties, vous n’avez pas besoin de placer manuellement _CrtDumpMemoryLeaks à chaque point de sortie. Pour provoquer un appel automatique à _CrtDumpMemoryLeaks à chaque point de sortie, passez un appel à _CrtSetDbgFlag au début de votre application avec les champs de bits indiqués ici :

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

Par défaut, _CrtDumpMemoryLeaks affiche un rapport des fuites de mémoire dans le volet Débogage de la fenêtre Sortie . Si vous utilisez une bibliothèque, celle-ci peut redéfinir la sortie vers un autre emplacement.

Vous pouvez utiliser _CrtSetReportMode pour rediriger le rapport vers un autre emplacement ou revenir à la fenêtre Sortie, comme illustré ici :

_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG );

L’exemple suivant montre une fuite de mémoire simple et affiche des informations de fuite de mémoire à l’aide _CrtDumpMemoryLeaks();de .

// 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();
}

Interpréter le rapport des fuites de mémoire

Si votre application ne définit pas _CRTDBG_MAP_ALLOC, _CrtDumpMemoryLeaks affiche un rapport des fuites de mémoire similaire à celui-ci :

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 votre application définit _CRTDBG_MAP_ALLOC, le rapport des fuites de mémoire ressemblera à ceci :

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.

Le deuxième rapport indique le nom du fichier et le numéro de ligne où la mémoire perdue est d’abord allouée.

Que vous définissiez _CRTDBG_MAP_ALLOCou non, le rapport de fuite de mémoire affiche :

  • Le numéro d’allocation de mémoire, en l’occurrence 18
  • Le type de bloc, normal dans l’exemple.
  • L’emplacement de stockage hexadécimal, en l’occurrence 0x00780E80.
  • La taille du bloc, en l’occurrence 64 bytes.
  • les 16 premiers octets de données du bloc, au format hexadécimal.

Les types de blocs de mémoire sont normaux, client ou CRT. Un bloc normal est un bloc de mémoire ordinaire alloué par votre programme. Un bloc client est un type de bloc de mémoire spécial utilisé par les programmes MFC pour les objets qui nécessitent un destructeur. Suivant l'objet créé, l'opérateur MFC new crée soit un bloc normal, soit un bloc client.

Un bloc CRT est alloué par la bibliothèque CRT pour ses propres besoins. La bibliothèque CRT gère la désallocation de ces blocs, de sorte que les blocs CRT n’apparaissent pas dans le rapport de fuite de mémoire, sauf s’il existe de graves problèmes avec la bibliothèque CRT.

Il existe deux autres types de blocs de mémoire qui n'apparaissent jamais dans les rapports de fuite de mémoire. Un bloc libre est la mémoire qui a été libérée, donc par définition n’est pas divulguée. Ensuite, le bloc ignore, qui correspond à de la mémoire explicitement marquée comme étant à exclure du rapport des fuites de mémoire.

Les techniques précédentes identifient les fuites de mémoire pour la mémoire allouée à l’aide de la fonction CRT malloc standard. Toutefois, si votre programme alloue de la mémoire à l’aide de l’opérateur C++ new, vous pouvez voir uniquement le nom de fichier et le numéro de ligne où operator new les appels _malloc_dbg sont effectués dans le rapport de fuite de mémoire. Pour créer un rapport de fuite de mémoire plus utile, vous pouvez écrire une macro comme la suivante pour signaler la ligne qui a effectué l’allocation :

#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

Vous pouvez maintenant remplacer l’opérateur new à l’aide de la macro DBG_NEW dans votre code. Dans les builds de débogage, DBG_NEW utilise une surcharge globale qui prend des paramètres supplémentaires pour le type de bloc, le fichier et le numéro de operator new ligne. Surcharge des new appels _malloc_dbg pour enregistrer les informations supplémentaires. Les rapports de fuite de mémoire affichent le nom de fichier et le numéro de ligne où les objets divulgués ont été alloués. Les builds de mise en production utilisent toujours la valeur new par défaut. Vous trouverez ci-dessous un exemple de la technique :

// 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();
}

Lorsque vous exécutez ce code dans le débogueur Visual Studio, l’appel à _CrtDumpMemoryLeaks génère un rapport dans la fenêtre Sortie qui ressemble à ceci :

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.

Cette sortie indique que l’allocation divulguée se trouvait à la ligne 20 de debug_new.cpp.

Notes

Nous vous déconseillons de créer une macro de préprocesseur nommée newou tout autre mot clé du langage.

Définir les points d’arrêt sur un numéro d’allocation de mémoire

Le numéro d'allocation de mémoire vous indique lorsqu'un bloc de mémoire perdue a été alloué. Un bloc dont le numéro d’allocation de mémoire est égal à 18 correspond au 18e bloc de mémoire alloué pendant l’exécution de l’application. Le rapport CRT compte toutes les allocations de blocs de mémoire pendant l’exécution, y compris les allocations par la bibliothèque CRT et d’autres bibliothèques telles que MFC. Par conséquent, le bloc dont le numéro d’allocation de mémoire est égal à 18 ne correspond probablement pas au 18e bloc de mémoire alloué par votre code.

Vous pouvez utiliser le numéro d'allocation pour définir un point d'arrêt sur l'allocation de mémoire.

Pour définir un point d'arrêt d'allocation de mémoire à l'aide de la fenêtre Espion

  1. Définissez un point d’arrêt près du début de votre application et démarrez le débogage.

  2. Lorsque l’application s’interrompt au point d’arrêt, ouvrez une fenêtre Espion en sélectionnant Déboguer>Fenêtre>Espion 1 (ou Espion 2, Espion 3 ou Espion 4).

  3. Depuis la fenêtre Espion, tapez _crtBreakAlloc dans la colonne Nom.

    Si vous utilisez la version DLL multithread de la bibliothèque CRT (l’option /MD), ajoutez l’opérateur de contexte : {,,ucrtbased.dll}_crtBreakAlloc

    Assurez-vous que les symboles de débogage sont chargés. Sinon, _crtBreakAlloc il est signalé comme non identifié.

  4. Appuyez sur Entrée.

    Le débogueur évalue l'expression et place le résultat dans la colonne Valeur . Cette valeur est -1 si vous n’avez pas défini de points d’arrêt sur les allocations de mémoire.

  5. Remplacez la valeur dans la colonne Valeur par le numéro de l’allocation de mémoire où vous souhaitez que le débogueur effectue l’arrêt.

Une fois le point d’arrêt défini sur un numéro d’allocation de mémoire, poursuivez le débogage. Veillez à exécuter dans les mêmes conditions, afin que le numéro d’allocation de mémoire ne change pas. Lorsque votre programme s’arrête à l’allocation de mémoire spécifiée, utilisez la fenêtre Pile des appels et les autres fenêtres de débogage pour déterminer dans quelles conditions la mémoire a été allouée. Ensuite, vous pouvez poursuivre l’exécution pour observer ce qui arrive à l’objet et connaître la raison pour laquelle il n’a pas été désalloué.

La définition d’un point d’arrêt sur variable peut aussi s’avérer utile. Pour plus d’informations, consultez Utilisation des points d’arrêt.

Vous pouvez aussi définir des points d’arrêt d’allocation de mémoire dans le code. Vous pouvez définir :

_crtBreakAlloc = 18;

ou :

_CrtSetBreakAlloc(18);

Comparer les états de la mémoire

Une autre technique pour détecter les fuites de mémoire consiste à prendre des instantanés de l'état de la mémoire de l'application en certains points clés. Pour prendre un instantané de l’état de la mémoire à un point donné de votre application, créez une structure _CrtMemState et passez-la à la fonction _CrtMemCheckpoint.

_CrtMemState s1;
_CrtMemCheckpoint( &s1 );

La fonction _CrtMemCheckpoint remplit la structure avec un instantané de l’état actuel de la mémoire.

Pour afficher le contenu d’une structure _CrtMemState, passez-la à la fonction _ CrtMemDumpStatistics :

_CrtMemDumpStatistics( &s1 );

_CrtMemDumpStatistics crée un dump de l’état de la mémoire similaire à celui-ci :

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.

Pour déterminer si une fuite de mémoire a eu lieu dans une section de code, vous pouvez prendre des instantanés de l'état de la mémoire avant et après la section, puis utiliser _CrtMemDifference pour comparer les deux états :

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

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

_CrtMemDifference compare les états de mémoire s1 et s2 , puis retourne un résultat (s3) qui correspond à la différence entre s1 et s2.

L’une des techniques permettant de rechercher les fuites de mémoire consiste à appeler _CrtMemCheckpoint au début et à la fin de votre application, puis à utiliser _CrtMemDifference pour comparer les résultats. Si _CrtMemDifference indique une fuite de mémoire, vous pouvez ajouter davantage d’appels à _CrtMemCheckpoint pour diviser votre programme à l’aide d’une recherche binaire jusqu’à ce que vous ayez isolé la source des fuites.

Faux positifs

_CrtDumpMemoryLeaks peut donner de fausses indications de fuites de mémoire si une bibliothèque marque les allocations internes comme des blocs normaux au lieu de blocs CRT ou de blocs clients. Dans ce cas, _CrtDumpMemoryLeaks est incapable d'indiquer la différence entre les allocations d'utilisateur et les allocations de bibliothèque internes. Si les destructeurs globaux des allocations de la bibliothèque s'exécutent après le point d'appel à _CrtDumpMemoryLeaks, chaque allocation de bibliothèque interne est signalée comme une fuite de mémoire. Les versions de la bibliothèque de modèles standard antérieures à Visual Studio .NET peuvent entraîner _CrtDumpMemoryLeaks à signaler de tels faux positifs.

Voir aussi