11
Sep
2007

Atunci cand rulati o aplicatie cu o configuratie de debug in debugger-ul din Visual Studio (indiferent de versiune) puteti observa ca memoria e plina de valori precum 0xCD sau 0xDD. Acestea nu sunt intamplatoare si se datoreaza mecanismului creat de Microsoft pentru detectia coruptiei memoriei pe platforma Windows. In acest articol voi explica cum se aloca si dezaloca memoria prin new/delete sau malloc/free.

Valorile precum 0xCD sau 0xDD au o anumita semnificatie explicata in tabelul de mai jos:

Valoare Nume Descriere
0xCD Clean Memory Memorie alocata cu malloc sau new dar nescrisa de catre aplicatie.
0xDD Dead Memory Memorie eliberata cu delete sau free. Valoarea este folosita pentru a detecta scrieri prin pointeri suspendati (dangling).
0xFD Fence Memory Cunoscut si sub titulatura de "no man's land". Aceasta valoare e folosita pentru a ingradi memoria alocata (asemenea unui gard = fence) si folosita pentru a detecta scrieri in siruri peste limitele acestora.
0xAB (Allocated Block?) Memorie alocata cu LocalAlloc().
0xBAADF00D Bad Food Memorie alocata cu LocalAlloc() cu flag-ul LMEM_FIXED, dar inca nescrisa.
0xCC   Atunci cand codul este compilat cu optiunea /GZ, variabilelor neinitializate le este atribuita in mod implicit aceasta valoare (la nivel de byte).

Cateva din aceste valori sunt definite in fisierul DBGHEAP.H:

static unsigned char _bNoMansLandFill = 0xFD;   /* fill no-man's land with this */
static unsigned char _bDeadLandFill   = 0xDD;   /* fill free objects with this */
static unsigned char _bCleanLandFill  = 0xCD;   /* fill new objects with this */

Inainte de a continua trebuie enumerate functiile de management a memoriei referite in acest articol.

Functie Descriere
malloc Functie C/C++ care aloca un bloc de memorie pe heap. Implementarea operatorului new in C++ se bazeaza pe aceasta functie.
_malloc_dbg Versiune de debug pentru malloc; disponibila doar in varianta de debug a bibliotecilor de run-time. Cand _DEBUG nu e definit, apelurile la _malloc_dbg se transforma in apeluri la malloc. Amandoua functiile (malloc si _malloc_dbg) aloca un bloc de memorie pe heap, dar versiunea de debug vine cu cateva lucruri in plus: zone tampon in jurul memorie alocate pentru a detecta pierderi de memorie (memory leaks), informatii de bloc pentru a urmarii tipuri specifice de alocare, informatii de nume fisier si numar linie pentru determinarea originii alocarii.
free Functie C/C++ pentru eliberarea unui bloc alocat. Implementarea operatorului delete in C++ se bazeaza pe aceasta functie.
_free_dbg Versiunea de debug pentru free. disponibila doar in varianta de debug a bibliotecilor de run-time. Cand _DEBUG nu e definit, apelurile la _free_dbg se transforma in apeluri la free. Versiunea de debug ofera in plus posibilitatea de a pastra blocurile eliberate in lista inlantuita a heap-ului pentru a simula conditii de deficit de memorie si eliberarea unor tipuri specifice de alocari.
LocalAlloc, GlobalAlloc Functii Win32 pentru a aloca un numar specific de bytes pe heap. Managementul memoriei incepand cu platforma Win32 nu mai prevede doua heap-uri separate, unul local si unul global, astfel incat cele doua functii sunt identice.
LocalFree, GlobalFree Functii Win32 pentru a elibera un obiect de memorie local si a invalida handle-ul sau.
HeapAlloc Functie Win32 pentru a aloca un bloc de memorie pe heap, memorie care nu poate fi mutata.
HeapFree Functie Win32 pentru a elibera un bloc alocat de HeapAlloc sau HeapReAlloc.

Intrucant in acest articol subiectul este managementul memorie intr-un configuratie de debug, orice referire la fuctiile malloc sau free implica de fapt versiunea de debug a acestora, adica _malloc_dbg si _free_dbg.

Pentru a intelelge mai bine ce se intampla vom lua o mica bucata de cod si urmarii in debugger pas cu pas, ce se intampla cu memoria alocata.

int main(int argc, char* argv[])
{
   char *buffer = new char[12];
 
   delete [] buffer;
 
   return 0;
}

In codul de mai sus se aloca o zona de 12 bytes pe heap, dar CRT-ul (C Runtime library) aloca mai mult, prin impachetarea zonei "utilizator" cu informatii de intretinere. Pentru fiecare bloc alocat CRT-ul pastreaza informatii intr-o structura _CrtMemBlockHeader, definita in DBGINT.H:

#define nNoMansLandSize 4
 
typedef struct _CrtMemBlockHeader
{
        struct _CrtMemBlockHeader * pBlockHeaderNext;
        struct _CrtMemBlockHeader * pBlockHeaderPrev;
        char *                      szFileName;
        int                         nLine;
#ifdef _WIN64
        /* These items are reversed on Win64 to eliminate gaps in the struct
         * and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
         * maintained in the debug heap.
         */
        int                         nBlockUse;
        size_t                      nDataSize;
#else  /* _WIN64 */
        size_t                      nDataSize;
        int                         nBlockUse;
#endif  /* _WIN64 */
        long                        lRequest;
        unsigned char               gap[nNoMansLandSize];
        /* followed by:
         *  unsigned char           data[nDataSize];
         *  unsigned char           anotherGap[nNoMansLandSize];
         */
} _CrtMemBlockHeader;

Aceasta structura pastreaza urmatoarele informatii:

Atribut Descriere
pBlockHeaderNext Un pointer catre urmatorul bloc alocat, dar urmator inseamna blocul alocat anterior, intrucat lista este vazuta ca o stiva, cu ultimul bloc alocat in varf.
pBlockHeaderPrev Un pointer la blocul anterior. Aceasta inseamna blocul care a fost alocat dupa blocul curent.
szFileName Un pointer catre numele fisierlui din care s-a facut apelul la malloc, daca este cunoscut.
nLine Linia din fisierul indicat de szFileName la care s-a facut apelul la malloc, daca este cunoscuta.
nDataSize Numarul de octeti ceruti.
nBlockUse 0 - Bloc dealocat, dar neeliberat heap-ului Win32
1 - Bloc normal (alocat cu new/malloc)
2 - Blocuri CRT, alocate de CRT pentru propriul uz
lRequest Counter incrementat cu fiecare alocare.
gap O zone de 4 octeti umpluta cu 0xFD, imprejmuind zona alocata de nDataSize octeti. Un alt bloc de aceiasi dimensiune, scris cu 0xFD urmeaza blocului de date.

Cea mai mare parte a alocarii si eliberarii unui bloc revin functiilor HeapAlloc() si HeapFree(). Atunci cand cereri alocarea a 12 octeti pe heap, malloc la apela HeapAlloc() cerand inca 36 de octeti.

blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;

Pentru exemplul dat, malloc cere spatiu pentru 12 octeti ceruri de noi, plus 32 de octeti pentru structura _CrtMemBlockHeader si inca nNoMansLandSize octeti (4 octeti) pentru a imprejmui zona de date la sfarsit. Dar functia HeapAlloc() va aloca mai mult decat cei 36 de octeti: 8 octeti sub blocul cerut (adica la o adresa mai mica) si 32 de octeti deasupra sa (adica la o adresa mai mare). De asemenea, initializeaza blocul cerul de malloc cu 0xBAAFF00D. Dupa ce HeapAlloc() isi termina treaba, malloc scrie informatiile in blocul _CrtMemBlockHeader si initializeaza zona de date (in cazul nostru cei 12 octeti ceruri) cu 0xCD si zona nimanui, de imprejmuire, cu 0xFD.

Tabelul de mai jos arata continutul memoriei dupa apelul la HeapFree si dupa apelul la malloc. (Adresele sunt doar pentru usurinta intelegerii, ele pot varia de la o rulare la alta).

Adresa dupa HeapAlloc() dupa malloc()

00320FD8
00320FDC
00320FE0
00320FE4
00320FE8
00320FEC
00320FF0
00320FF4
00320FF8
00320FFC
00321000
00321004
00321008
0032100C
00321010
00321014
00321018
0032101C
00321020
00321024
00321028
0032102C

09 00 09 01
E8 07 18 00
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
0D F0 AD BA
AB AB AB AB
AB AB AB AB
00 00 00 00
00 00 00 00
79 00 09 00
EE 04 EE 00
40 05 32 00
40 05 32 00

09 00 09 01
E8 07 18 00
98 07 32 00
00 00 00 00
00 00 00 00
00 00 00 00
0C 00 00 00
01 00 00 00
2E 00 00 00
FD FD FD FD
CD CD CD CD
CD CD CD CD
CD CD CD CD
FD FD FD FD
AB AB AB AB
AB AB AB AB
00 00 00 00
00 00 00 00
79 00 09 00
EE 04 EE 00
40 05 32 00
40 05 32 00

Legenda culori:

  • verde: informatii de administrare pentru win32
  • albastru: blocul cerut de malloc si scris cu BAAFD00D
  • mov: bloc _CrtMemBlockHeader
  • rosu: zona de imprejmuire
  • negru: blocul de date cerut (scris cu CD)

In exemplul anterior, blocul de date cerut pentru a fi alocat pe heap, se gaseste incepand cu adresa 0x00321000.

Atunci cand se elibereaza un block (cu delete/free), CRT-ul va scrie in blocul cerut de la HeapAlloc() cu 0xDD (dead memory) pentru a indica ca este o zona libera (sau moarta). In mod normal free va apela HeapFree() pentru a da inapoi blocul alocat heap-ului Win32, in care caz blocul va fi suprascris cu 0xFEEEEEEE pentru a indica o zona de memorie libera pe heap-ul Win32.

Apelarea lui HeapFree poate fi evitata folosit flag-ul CRTDBG_DELAY_FREE_MEM_DF cu _CrtSetDbgFlag(). Aceasta previne eliberarea memorie catre heap-ul Win32. Can acet flag este setat, blocurile eliberate sunt pastrate in lista inlantuita a CRT-ului, dar sunt marcate cu _FREE_BLOCK. Acest lucru este util pentru a verifica scrieri cu pointeri suspendati (dangling), prin verificarea patter-unului 0xDD. Pentru a verifica integritatea heap-ului se poate folosi functia _CrtCheckMemory().

Tabelul de mai jos arata continutul memoriei in timpul apelului la free, inainte de apelarea lui HeapFree, si dupa aceasta.

Adresa Inainte de HeapFree() Dupa HeapFree()

00320FD8
00320FDC
00320FE0
00320FE4
00320FE8
00320FEC
00320FF0
00320FF4
00320FF8
00320FFC
00321000
00321004
00321008
0032100C
00321010
00321014
00321018
0032101C
00321020
00321024
00321028
0032102C

09 00 09 01
5E 07 18 00
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
DD DD DD DD
AB AB AB AB
AB AB AB AB
00 00 00 00
00 00 00 00
79 00 09 00
EE 04 EE 00
40 05 32 00
40 05 32 00

82 00 09 01
5E 04 18 00
E0 2B 32 00
78 01 32 00
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE
EE FE EE FE

Legenda culori:

  • verde: informatii de administrare pentru win32
  • albastru: bloc CRT suprascris cu 0xDD
  • gri: memorie inapoiata heap-ului Win32

Cele doua tabele de mai sus sunt unite in cel de mai jos:

Adresa (hexa) Ofset HeapAlloc malloc Free inainte de HeapFree Free dupa HeapFree Descriere

00320FD8

00320FDC

00320FE0

00320FE4

00320FE8

00320FEC

00320FF0

00320FF4

00320FF8

00320FFC

00321000

00321004

00321008

0032100C

00321010

00321014

00321018

0032101C

00321020

00321024

00321028

0032102C

-40

-36

-32

-28

-24

-20

-16

-12

-8

-4

0

+4

+8

+12

+16

+20

+24

+28

+32

+36

+40

+44

01090009

001807E8

BAADF00D

BAADF00D

BAADF00D

BAADF00D

BAADF00D

BAADF00D

BAADF00D

BAADF00D

BAADF00D

BAADF00D

BAADF00D

BAADF00D

ABABABAB

ABABABAB

00000000

00000000

00090079

00EE04EE

00320540

00320540

01090009

001807E8

00320798

00000000

00000000

00000000

0000000C

00000001

0000002E

FDFDFDFD

CDCDCDCD

CDCDCDCD

CDCDCDCD

FDFDFDFD

ABABABAB

ABABABAB

00000000

00000000

00090079

00EE04EE

00320540

00320540

01090009

0018075E

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

DDDDDDDD

ABABABAB

ABABABAB

00000000

00000000

00090079

00EE04EE

00320540

00320540

01090082

0018045E

00322BE0

00320178

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

FEEEEEEE

Info Heap Win32

Info Heap Win32

pBlockHeaderNext

pBlockHeaderPrev

szFileName

nLine

nDataSize

nBlockUse

lRequest

gap (no mans land)

Zona date cerute

Zona date cerute

Zona date cerute

No mans land

Info Heap Win32

Info Heap Win32

Info Heap Win32

Info Heap Win32

Info Heap Win32

Info Heap Win32

Info Heap Win32

Info Heap Win32