2012-02-06 14 views
5

Sto tentando di utilizzare realloc() in un'applicazione Windows. Sto allocando un grande blocco di memoria, quindi usando realloc() per ridurlo più tardi una volta che conosco la dimensione corretta.Realloc() non libera correttamente la memoria in Windows

Sto trovando che sebbene realloc() sembra funzionare correttamente (la memoria in Task Manager riflette ciò che ci si aspetterebbe) l'applicazione alla fine esaurisce la memoria. Da quello che posso dire, è come se relloc() libera la memoria ma non libera lo spazio di indirizzo virtuale associato alla memoria. Di conseguenza, lo malloc() non funzionerà.

Ecco una piccola applicazione console che illustra il problema:

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    static const DWORD dwAllocSize = (50 * 1024 * 1024); 
    static const DWORD dwReallocSize = 10240; 
    static const DWORD dwMaxIterations = 200; 

    BYTE* arpMemory[dwMaxIterations]; 
    memset(arpMemory, 0, sizeof(arpMemory)); 

    for(DWORD i = 0; i < dwMaxIterations; i++) 
    { 
     arpMemory[i] = (BYTE*) malloc(dwAllocSize); 
     if(!arpMemory[i]) 
     { 
      printf("OUT OF MEMORY after %d iterations!\n", i); 
      return -1; 
     } 

     BYTE* pRealloc = (BYTE*) realloc(arpMemory[i], dwReallocSize); 
     if(!pRealloc) 
     { 
      printf("Realloc FAILED after %d iterations!\n", i); 
      return -1; 
     } 
     else if(pRealloc != arpMemory[i]) 
     { 
      printf("Warning: Pointer changed: 0x%08X -> 0x%08X\n", arpMemory[i], pRealloc); 
      arpMemory[i] = pRealloc; 
     } 
    } 

    printf("Success!\n"); 

    for(int i = 0; i < dwMaxIterations; i++) 
     free(arpMemory[i]); 

    return 0; 
} 

L'applicazione assegna ripetutamente 50 MB di memoria e poi subito ridimensiona essere solo 10K. Se lo esegui, troverai che fallisce con un errore OUT OF MEMORY dopo solo 38 iterazioni. Ciò corrisponde a 2 GB di memoria allocata in origine, ovvero il limite di spazio degli indirizzi per le applicazioni Windows.

È interessante notare che, se si cerca in Task Manager, l'applicazione non richiede quasi memoria. Eppure malloc() non funziona. Questo è ciò che mi porta a credere che lo spazio virtuale degli indirizzi sia esaurito.

(Un altro esperimento per provare è quello di commentare la riallocazione, in modo che nessun memoria viene liberata o riassegnati L'applicazione non riesce esattamente nello stesso posto:.. Dopo 38 iterazioni L'unica differenza è che questa volta Task Manager riflette il pieno 2GB in uso.)

Un ultimo punto di informazioni: questa stessa applicazione funziona sotto Linux. Quindi questo problema di realloc() è strettamente solo per Windows.

Qualche idea?

+1

Qual è la tua domanda? – ikegami

+0

La mia domanda? Immagino che "questo comportamento sia un bug noto in Windows?" E ovviamente, c'è qualche soluzione per questo problema che non implica la copia della memoria? – asheffie

+0

Ad ogni modo, probabilmente hai un problema di progettazione fondamentale, certamente per i processi che metteranno a dura prova lo spazio degli indirizzi a 32 bit. Dici "Sto allocando un grande blocco di memoria, quindi usando realloc() per ridurlo più tardi una volta che conosco la dimensione corretta." Questo è solo un problema. È molto impegnativo richiedere blocchi contigui di spazio indirizzi molto grandi. Sta molto meglio allocando piccoli pezzi di memoria e poi unendoli insieme. –

risposta

4

Si sta frammentando l'heap facendo questo. Qualsiasi cosa tu rilasci con realloc() viene aggiunta all'elenco dei blocchi liberi. Mai più da usare perché chiedi sempre un nuovo blocco più grande di quello. Questi blocchi liberi si accumuleranno semplicemente occupando la memoria virtuale finché non ne rimarrà più. Succede abbastanza velocemente quando passi via quasi 50 megabyte alla volta.

Dovrai ripensare al tuo approccio.

+0

Grazie per l'eccellente aiuto! Come ho già detto nel thread precedente, a questo punto vedo tre soluzioni: 1) Effettuare un'allocazione "massima dimensione" una tantum per i dati in entrata e subire il colpo di prestazioni di una copia di memoria in un buffer più piccolo (una volta che sappiamo la dimensione effettiva) quando siamo pronti a trasmettere i dati. 2) Fornire un ragionevole metodo "buffer size hint" in modo da non sovra-allocare una quantità ridicola di memoria. 3) Verifica se è possibile spostare l'intero progetto per l'esecuzione nello spazio degli indirizzi a 64 bit. – asheffie

+0

Basta fare il contrario. Avvia small, realloc() quando hai bisogno di più. Raddoppiare l'allocazione è l'approccio standard. –

+0

Hans, non penso che questo problema sia legato alla frammentazione. Ho provato a ridurre le dimensioni di ogni richiesta successiva, in modo che si adattasse allo spazio liberato da quello precedente e non ha fatto alcuna differenza. –

2

Dopo alcuni esperimenti e la lettura tra le righe della documentazione, sono giunto alla conclusione che allocazioni di memoria di grandi dimensioni (leggermente inferiori a 512 k per 32 bit, leggermente inferiori a 1 MB per 64 bit) utilizzano lo spazio indirizzo assegnato utilizzando VirtualAlloc e riservato per quel particolare blocco di memoria.

Se non è pratico aumentare la dimensione del buffer al volo come suggerisce Hans, e non si desidera copiare i dati, penso che l'unica altra opzione è riservare un blocco di spazio di indirizzamento abbastanza grande per tutti i buffer e allocare lo spazio da te stesso. Quanto sarebbe complicato dipendere da fattori come il modo in cui l'applicazione utilizza e libera i buffer, se si scrive più di un buffer alla volta e su quanti dati si elaboreranno nel corso della vita dell'applicazione.

aggiuntive: qui è un codice di prova che mostra quello che vedo accadendo:

#include <windows.h> 

#include <stdio.h> 

DWORD reallocSize = 0x01000; // 4K 

void walkheap(HANDLE heap) 
{ 
    PROCESS_HEAP_ENTRY phe; 
    MEMORY_BASIC_INFORMATION mbi; 

    phe.lpData = NULL; 

    for (;;) 
    { 
     if (!HeapWalk(heap, &phe)) 
     { 
      printf("HeapWalk: %u\n", GetLastError()); 
      return; 
     } 
     printf("%08x %08x %08x %08x %08x ", phe.lpData, phe.cbData, phe.cbOverhead, phe.iRegionIndex, phe.wFlags); 
     if (VirtualQuery(phe.lpData, &mbi, sizeof(mbi)) != 0) 
     { 
      printf("--> %08x\n",mbi.AllocationBase); 
     } 
     else 
     { 
      printf("--> (error %u)\n", GetLastError()); 
     } 
    } 
} 

void alloc(HANDLE heap, DWORD count, DWORD size) 
{ 
    BYTE* ptr; 
    BYTE* pRealloc; 

    ptr = (BYTE *)HeapAlloc(heap, 0, size); 
    printf("Pointer %u is %08x (%08x)\n", count, ptr, size); 

    pRealloc = (BYTE*) HeapReAlloc(heap, 0, ptr, reallocSize); 
    if(pRealloc != ptr) 
    { 
     printf("Pointer %u changed to %08x\n", count, pRealloc); 
    } 
} 

int main(int argc, char ** argv) 
{ 
    HANDLE heap; 

    heap = HeapCreate(0, 0, 0); 
    if (heap == NULL) 
    { 
     printf("HeapCreate: %u\n", GetLastError()); 
     return 1; 
    } 

    walkheap(heap); 

    alloc(heap, 1, 0x08000); 
    alloc(heap, 2, 0x08000); 
    alloc(heap, 3, 0x08000); 
    alloc(heap, 4, 0x08000); 

    alloc(heap, 10, 0x20000000); 
    alloc(heap, 11, 0x20000000); 
    alloc(heap, 12, 0x20000000); 
    alloc(heap, 13, 0x20000000); 

    alloc(heap, 20, 0x10000000); 
    alloc(heap, 21, 0x10000000); 
    alloc(heap, 22, 0x10000000); 
    alloc(heap, 23, 0x10000000); 

    walkheap(heap); 

    return 0; 
} 

ei miei risultati (vedere la struttura PROCESS_HEAP_ENTRY):

Address Alloc Overhead Region Flags  Virtual Address Range Base 

00420000 00000588 00000000 00000000 00000001 --> 00420000 
004207e8 000007f8 00000010 00000000 00000000 --> 00420000 
00421000 0003f000 00000000 00000000 00000002 --> 00420000 
HeapWalk: 259 
Pointer 1 is 004207e0 (00008000) 
Pointer 2 is 004217f8 (00008000) 
Pointer 3 is 00422810 (00008000) 
Pointer 4 is 00423828 (00008000) 
Pointer 10 is 00740020 (20000000) 
Pointer 11 is 20750020 (20000000) 
Pointer 12 is 52580020 (20000000) 
Pointer 13 is 00000000 (20000000) 
Pointer 20 is 40760020 (10000000) 
Pointer 21 is 00000000 (10000000) 
Pointer 22 is 00000000 (10000000) 
Pointer 23 is 00000000 (10000000) 
00420000 00000588 00000000 00000000 00000001 --> 00420000 
004207e0 00001000 00000018 00000000 00000004 --> 00420000 
004217f8 00001000 00000018 00000000 00000004 --> 00420000 
00422810 00001000 00000018 00000000 00000004 --> 00420000 
00423828 00001000 00000018 00000000 00000004 --> 00420000 
00424848 0000e798 00000010 00000000 00000000 --> 00420000 
00433000 0002d000 00000000 00000000 00000002 --> 00420000 
00740020 00001000 00000020 00000040 00000024 --> 00740000 
20750020 00001000 00000020 00000040 00000024 --> 20750000 
52580020 00001000 00000020 00000040 00000024 --> 52580000 
40760020 00001000 00000020 00000040 00000024 --> 40760000 
HeapWalk: 259 

Si può notare che la piccola le allocazioni sono compresse saldamente, ma le grandi allocazioni sono tutte allocate in indirizzi virtuali separati. Lo spazio libero nelle allocazioni separate non viene utilizzato. Inoltre, solo l'allocazione principale dell'indirizzo virtuale ha blocchi di heap contrassegnati come spazio libero (flag uguale a 0 o 2).

Problemi correlati