Details zum CRT-Debugheap

Der CRT-Debug-Heap und verwandte Funktionen bieten viele Möglichkeiten zum Nachverfolgen und Debuggen von Speicherverwaltungsproblemen in Ihrem Code. Sie können sie verwenden, um Pufferüberläufe zu finden und berichte zu Speicherzuweisungen und Speicherstatus zu verfolgen und zu melden. Außerdem unterstützt sie das Erstellen eigener Debugzuordnungsfunktionen für Ihre individuellen App-Anforderungen.

Suchen von Pufferüberläufen mit Debugheap

Zwei der am häufigsten auftretenden und langwierigen Probleme überschreiben das Ende eines zugeordneten Puffers und Speicherverlustes (wenn keine Zuordnungen mehr erforderlich sind). Der Debugheap stellt leistungsfähige Tools bereit, die derartige Speicherbelegungsprobleme beheben helfen.

Durch die Debugversionen der Heapfunktionen wird die Standard- oder Basisversion aufgerufen, die in Releasebuilds verwendet wird. Wenn Sie einen Speicherblock anfordern, weist der Debug-Heap-Manager dem Basishap einen etwas größeren Speicherblock zu, als Sie angefordert haben, und gibt einen Zeiger auf den Teil dieses Blocks zurück. Angenommen, die Anwendung enthält den malloc( 10 )-Aufruf. In einem Releasebuild würde die Basis-Heap-Zuordnungsroutine aufgerufen, malloc die eine Zuordnung von 10 Bytes anfordert. In einem Debugbuild würde jedoch aufgerufen_malloc_dbg, malloc was dann die Basis-Heap-Zuordnungsroutine aufruft, die eine Zuordnung von 10 Bytes plus ca. 36 Bytes zusätzlichen Arbeitsspeicher anfordert. Alle resultierenden Speicherblöcke im Debugheap werden über eine einzelne verknüpfte Liste verbunden und sind nach ihrem Reservierungszeitpunkt angeordnet.

Der zusätzliche Arbeitsspeicher, der von den Debug-Heap-Routinen zugewiesen wird, wird für Buchführungsinformationen verwendet. Es enthält Zeiger, mit denen Debugspeicherblöcke miteinander verknüpft werden, und kleine Puffer auf beiden Seiten der Daten, um Überschreibungen der zugeordneten Region abzufangen.

Derzeit wird die Blockheaderstruktur zum Speichern der Buchführungsinformationen des Debug-Heaps im <crtdbg.h> Header deklariert und in der <debug_heap.cpp> CRT-Quelldatei definiert. Konzeptionell ähnelt es dieser Struktur:

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;

Die no_mans_land Puffer auf beiden Seiten des Benutzerdatenbereichs des Blocks sind derzeit 4 Byte groß und werden mit einem bekannten Bytewert gefüllt, der von den Debug-Heap-Routinen verwendet wird, um zu überprüfen, ob die Grenzwerte des Speicherblocks des Benutzers nicht überschrieben wurden. Der Debugheap schreibt zusätzlich einen definierten Wert in neue Speicherblöcke. Wenn Sie sich entscheiden, freigegebene Blöcke in der verknüpften Liste des Heaps beizubehalten, werden diese freigegebenen Blöcke auch mit einem bekannten Wert gefüllt. Derzeit werden die folgenden Bytewerte verwendet:

no_mans_land (0xFD)
Die Puffer "no_mans_land" auf beiden Seiten des von einer Anwendung verwendeten Arbeitsspeichers werden derzeit mit 0xFD gefüllt.

Freigegebene Blöcke (0xDD)
Die freigegebenen Blöcke, die in der verknüpften Heapliste unbenutzt bleiben, wenn das _CRTDBG_DELAY_FREE_MEM_DF-Flag festgelegt ist, enthalten derzeit den Wert 0xDD.

Neue Objekte (0xCD)
Neue Objekte werden mit 0xCD gefüllt, wenn sie zugewiesen werden.

Blocktypen auf dem Debugheap

Jeder Speicherblock im Debugheap hat einen von fünf möglichen Reservierungstypen. Die Typen werden abhängig von der jeweiligen Aufgabe, z. B. Erkennung von Speicherverlusten und Erstellung von Zustandsberichten, auf unterschiedliche Weise nachverfolgt und ausgegeben. Sie können den Typ eines Blocks angeben, indem Sie ihn mithilfe eines direkten Aufrufs an eine der Debug-Heap-Zuordnungsfunktionen wie z _malloc_dbg. B. zuordnen. Die fünf Arten von Speicherblöcken im Debug-Heap (im nBlockUse Element der _CrtMemBlockHeader Struktur festgelegt) sind wie folgt:

_NORMAL_BLOCK
Ein Aufruf oder malloc calloc erstellt einen Normalblock. Wenn Sie nur normale Blöcke verwenden möchten und keine Client-Blöcke benötigen, sollten Sie definieren _CRTDBG_MAP_ALLOC. _CRTDBG_MAP_ALLOC bewirkt, dass alle Heap-Zuordnungsaufrufe ihren Debugäquivalenten in Debugbuilds zugeordnet werden. Sie ermöglicht die Speicherung von Dateinamen- und Zeilennummerninformationen zu jedem Zuordnungsaufruf im entsprechenden Blockheader.

_CRT_BLOCK
Die Speicherblöcke, die intern von zahlreichen Laufzeitbibliotheksfunktionen reserviert werden, sind als CRT-Blöcke gekennzeichnet und können daher gesondert behandelt werden. Daher können die Leckerkennung und andere Vorgänge von ihnen nicht betroffen bleiben. CRT-Blöcke werden zu keiner Zeit durch eine Reservierungsoperation zugeordnet, erneut reserviert oder freigegeben.

_CLIENT_BLOCK
Eine Anwendung kann eine bestimmte Reservierungsgruppe zu Debugzwecken auf besondere Weise nachverfolgen, indem sie diese unter Verwendung expliziter Debugheap-Funktionsaufrufe als Speicherblöcke eines bestimmten Typs reserviert. MFC weist beispielsweise alle CObject Objekte als Clientblöcke zu. Andere Anwendungen behalten möglicherweise unterschiedliche Speicherobjekte in Clientblöcken bei. Um die Nachverfolgung feiner abzustufen, können auch Untertypen von Clientblöcken definiert werden. Untertypen von Clientblöcken werden festgelegt, indem Sie die Zahl um 16 Bits nach links verschieben und eine OR-Operation mit _CLIENT_BLOCK ausführen. Zum Beispiel:

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

Eine vom Client bereitgestellte Hook-Funktion zum Dumping der in Clientblöcken gespeicherten Objekte kann mithilfe _CrtSetDumpClientvon Clientblöcken installiert werden und wird dann aufgerufen, wenn ein Clientblock von einer Debugfunktion abgebildet wird. Außerdem kann eine bestimmte Funktion aufgerufen werden, _CrtDoForAllClientObjects die von der Anwendung für jeden Clientblock im Debug-Heap bereitgestellt wird.

_FREE_BLOCK
Normalerweise werden freigegebene Blöcke aus der Liste entfernt. Um zu überprüfen, in ob freier Arbeitsspeicher nicht geschrieben wird, oder um niedrige Speicherbedingungen zu simulieren, können Sie freigegebene Blöcke in der verknüpften Liste beibehalten, als "Frei" gekennzeichnet und mit einem bekannten Bytewert gefüllt werden (derzeit 0xDD).

_IGNORE_BLOCK
Es ist möglich, die Debug-Heap-Vorgänge für ein bestimmtes Intervall zu deaktivieren. In diesem Zeitraum werden die Speicherblöcke zwar in der Liste geführt, aber als ignorierte Blöcke gekennzeichnet.

Um den Typ und Untertyp eines bestimmten Blocks zu bestimmen, verwenden Sie die Funktion _CrtReportBlockType und die Makros _BLOCK_TYPE und _BLOCK_SUBTYPE. Die Makros sind wie <crtdbg.h> folgt definiert:

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

Überprüfen auf Heapintegrität und Speicherverluste

Auf viele Features des Debugheaps muss über den Code zugegriffen werden. Im folgenden Abschnitt werden einige Funktionen und ihre Verwendung beschrieben.

_CrtCheckMemory
Sie können z. B. einen Aufruf _CrtCheckMemoryverwenden, um die Integrität des Heaps jederzeit zu überprüfen. Diese Funktion prüft jeden Speicherblock im Heap. Es überprüft, ob die Informationen zum Speicherblockheader gültig sind, und bestätigt, dass die Puffer nicht geändert wurden.

_CrtSetDbgFlag
Sie können steuern, wie der Debug-Heap die Zuordnungen mithilfe eines internen Flags nachverfolgt, _crtDbgFlagdas mithilfe der _CrtSetDbgFlag Funktion gelesen und festgelegt werden kann. Indem Sie dieses Flag ändern, können Sie den Debugheap anweisen, beim Beenden des Programms mögliche Speicherverluste zu ermitteln und ggf. einen Speicherverlustbericht auszugeben. Ebenso können Sie den Heap anweisen, freizugebende Speicherblöcke in der verknüpften Liste zu hinterlassen, um Situationen mit geringem Arbeitsspeicher zu simulieren. Wenn der Heap überprüft wird, werden diese freigegebenen Blöcke vollständig geprüft, um sicherzustellen, dass sie nicht gestört wurden.

Das _crtDbgFlag Flag enthält die folgenden Bitfelder:

Bitfeld Standardwert Beschreibung
_CRTDBG_ALLOC_MEM_DF Ein Aktiviert die Debugreservierung. Wenn dieses Bit deaktiviert ist, bleiben Zuordnungen miteinander verkettet, aber ihr Blocktyp ist _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Aus Verhindert die Freigabe von Speicher, um beispielsweise Speichermangel zu simulieren. Wenn dieses Bit aktiviert ist, werden freigegebene Blöcke in der verknüpften Liste des Debug-Heaps beibehalten, aber mit _FREE_BLOCK einem speziellen Bytewert gekennzeichnet und ausgefüllt.
_CRTDBG_CHECK_ALWAYS_DF Aus Ursachen _CrtCheckMemory , die bei jeder Zuordnung und Zuordnung aufgerufen werden. Die Ausführung ist langsamer, aber fehler werden schnell erfasst.
_CRTDBG_CHECK_CRT_DF Aus Bewirkt, dass Blöcke, die als Typ _CRT_BLOCK gekennzeichnet sind, in Leckerkennungs- und Zustandsdifferenzvorgängen eingeschlossen werden. Wenn dieses Bit deaktiviert ist, wird der von der Laufzeitbibliothek intern verwendete Speicher während solcher Operationen ignoriert.
_CRTDBG_LEAK_CHECK_DF Aus Bewirkt, dass die Lecküberprüfung beim Beenden des Programms über einen Aufruf _CrtDumpMemoryLeaksdurchgeführt wird. Wenn die Anwendung nicht den gesamten belegten Speicher freigeben konnte, wird ein Fehlerbericht generiert.

Konfigurieren des Debugheaps

Alle Aufrufe an Heapfunktionen, wie z. B. malloc, free, calloc, realloc, new und delete, werden in die Debugversionen der Funktionen aufgelöst, die auf dem Debugheap arbeiten. Wenn Sie einen Speicherblock freigeben, überprüft der Debugheap automatisch die Pufferintegrität auf beiden Seiten des reservierten Bereichs und erstellt einen Fehlerbericht, falls über den Puffer hinaus geschrieben wurde.

So verwenden Sie den Debugheap

  • Verknüpfen Sie den Debugbuild Ihrer Anwendung mit einer Debugversion der C-Laufzeitbibliothek.

So ändern Sie ein oder _crtDbgFlag mehrere Bitfelder, und erstellen Sie einen neuen Zustand für das Flag

  1. Rufen Sie _CrtSetDbgFlag auf, wobei der newFlag-Parameter auf _CRTDBG_REPORT_FLAG festgelegt ist (um den aktuellen _crtDbgFlag-Zustand zu erhalten), und speichern Sie den Rückgabewert in einer temporären Variablen.

  2. Aktivieren Sie alle Bits mithilfe eines bitweisen | Operators ("oder") für die temporäre Variable mit den entsprechenden Bitmasken (dargestellt im Anwendungscode durch Manifestkonstanten).

  3. Deaktivieren Sie die anderen Bits, indem Sie einen bitweisen & Operator ("und") für die Variable mit einem bitweisen ~ Operator ("nicht" oder Ergänzung) der entsprechenden Bitmasken verwenden.

  4. Rufen Sie _CrtSetDbgFlag auf, wobei der newFlag-Parameter auf den in der temporären Variablen gespeicherten Wert festgelegt ist, um den neuen Zustand von _crtDbgFlag festzulegen.

    Die folgenden Codezeilen ermöglichen z. B. die automatische Leckerkennung und deaktivieren Überprüfungen auf Blöcke des Typs _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, deleteund _CLIENT_BLOCK Zuordnungen im C++-Debug-Heap

Die Debugversionen der C-Laufzeitbibliothek enthalten Debugversionen der new- und delete-C++-Operatoren. Bei Verwendung des _CLIENT_BLOCK-Zuweisungstyps müssen Sie die Debugversion des new-Operators direkt aufrufen oder Makros erstellen, die den new-Operator im Debugmodus ersetzen, wie im folgenden Beispiel dargestellt:

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

Die Debugversion des Operators delete funktioniert mit allen Blocktypen und macht daher bei der Kompilierung einer Releaseversion keine Programmänderungen erforderlich.

Heap-Statusberichtsfunktionen

Um eine Zusammenfassungsmomentaufnahme des Heap-Zustands zu einem bestimmten Zeitpunkt zu erfassen, verwenden Sie die _CrtMemState in <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;

Diese Struktur speichert einen Zeiger auf den ersten (zuletzt reservierten) Block in der verknüpften Heapliste. Anschließend werden in zwei Arrays die Anzahl der einzelnen Speicherblocktypen (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCKusw.) in der Liste und die Anzahl der in jedem Blocktyp zugeordneten Bytes erfasst. Schließlich zeichnet die Struktur auf, wie viele Bytes bisher maximal gleichzeitig im Heap reserviert waren und wie viele Bytes momentan reserviert sind.

Weitere CRT-Berichterstellungsfunktionen

Die folgenden Funktionen geben den Heapzustand und -inhalt wieder. Diese Informationen werden verwendet, um Speicherverluste und andere Probleme zu erkennen.

Funktion Beschreibung
_CrtMemCheckpoint Speichert eine Momentaufnahme des Heaps in einer _CrtMemState struktur, die von der Anwendung bereitgestellt wird.
_CrtMemDifference Vergleicht zwei Speicherzustandsstrukturen, speichert die Unterschiede in einer dritten Zustandsstruktur und gibt TRUE zurück, falls unterschiedliche Zustände festgestellt wurden.
_CrtMemDumpStatistics Dumps a given _CrtMemState structure. Die Struktur kann eine Zustandsmomentaufnahme des Debugheaps oder die Unterschiede zwischen zwei Momentaufnahmen enthalten.
_CrtMemDumpAllObjectsSince Gibt Informationen zu allen Objekten aus, die seit einer bestimmten Heapmomentaufnahme oder seit Beginn der Ausführung reserviert wurden. Jedes Mal, wenn sie einen _CLIENT_BLOCK Block abbildet, ruft es eine Hook-Funktion auf, die von der Anwendung bereitgestellt wird, wenn eine installiert _CrtSetDumpClientwurde.
_CrtDumpMemoryLeaks Stellt fest, ob seit dem Programmstart Speicherverluste aufgetreten sind, und gibt ggf. alle reservierten Objekte aus. Jedes Mal, wenn _CrtDumpMemoryLeaks ein _CLIENT_BLOCK Block abbildet, ruft es eine Hook-Funktion auf, die von der Anwendung bereitgestellt wird, wenn eine installiert wurde._CrtSetDumpClient

Nachverfolgen von Heap-Zuordnungsanforderungen

Das Kennen des Quelldateinamens und der Zeilennummer eines Assertions- oder Berichtsmakros ist häufig hilfreich, um die Ursache eines Problems zu ermitteln. Dasselbe gilt nicht so wahrscheinlich für Heap-Zuordnungsfunktionen. Sie können Makros zwar an vielen geeigneten Stellen in der Logikstruktur einer Anwendung einfügen, aber eine Zuordnung wird häufig in einer Funktion begraben, die von vielen verschiedenen Stellen zu vielen verschiedenen Zeiten aufgerufen wird. Die Frage ist nicht, welche Codezeile eine schlechte Zuordnung gemacht hat. Stattdessen war eine der Tausenden von Zuordnungen, die von dieser Codezeile vorgenommen wurden, schlecht und warum.

Eindeutige Zuordnungsanforderungsnummern und _crtBreakAlloc

Es gibt eine einfache Möglichkeit, den spezifischen Heap-Zuordnungsaufruf zu identifizieren, der schlecht war. Es nutzt die eindeutige Zuordnungsanforderungsnummer, die jedem Block im Debug-Heap zugeordnet ist. Wenn Blockinformationen über eine der Dumpfunktionen ausgegeben werden, wird die Zuweisungsanforderungsnummer in geschweiften Klammern angezeigt (Beispiel: {36}).

Sobald Sie die Zuordnungsanforderungsnummer eines nicht ordnungsgemäß zugewiesenen Blocks kennen, können Sie diese Nummer übergeben, um _CrtSetBreakAlloc einen Haltepunkt zu erstellen. Die Ausführung wird dann unmittelbar vor der Reservierung des betreffenden Blocks unterbrochen, und Sie können zurückverfolgen, welche Routine für den falschen Aufruf verantwortlich ist. Um eine erneute Kompilierung zu vermeiden, können Sie dasselbe im Debugger erreichen, indem Sie die Zuweisungsanforderungsnummer festlegen _crtBreakAlloc , an der Sie interessiert sind.

Erstellen von Debugversionen Ihrer Zuordnungsroutinen

Ein komplexerer Ansatz besteht darin, Debugversionen Ihrer eigenen Zuordnungsroutinen zu erstellen, vergleichbar mit den _dbg Versionen der Heap-Zuordnungsfunktionen. Sie können dann Quelldatei- und Zeilennummernargumente an die zugrunde liegenden Heap-Zuordnungsroutinen übergeben, und Sie können sofort sehen, wo eine ungültige Zuordnung stammt.

Angenommen, Ihre Anwendung enthält eine häufig verwendete Routine, die dem folgenden Beispiel ähnelt:

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

In einer Headerdatei können Sie Code wie das folgende Beispiel hinzufügen:

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

Als nächstes ändern Sie die Reservierung in der Routine zum Erstellen von Datensätzen wie folgt:

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

Nun werden der Quelldateiname und die Zeilennummer, in der addNewRecord aufgerufen wurde, in jedem dadurch reservierten Block im Debugheap gespeichert und beim Überprüfen des Blocks ausgegeben.

Siehe auch

Debuggen von nativem Code