Realloc () ne libère pas correctement la mémoire dans Windows

Je tente d’utiliser realloc () dans une application Windows. J’affecte un grand bloc de mémoire, puis j’utilise realloc () pour le réduire plus tard, une fois que je connais la taille correcte.

Je trouve que même si realloc () semble fonctionner correctement (la mémoire dans le Gestionnaire des tâches reflète ce que vous attendez), l’application finit par manquer de mémoire. D’après ce que je peux dire, c’est comme si relloc () libérait la mémoire mais ne libérait pas l’espace d’adressage virtuel associé à la mémoire. Par conséquent, malloc () échouera éventuellement.

Voici une petite application de console qui illustre le problème:

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  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’application alloue à plusieurs resockets 50 Mo de mémoire et la redimensionne immédiatement à 10 Ko seulement. Si vous l’exécutez, vous constaterez qu’il échoue avec une erreur OUT OF MEMORY après seulement 38 itérations. Cela correspond à 2 Go de mémoire allouée à l’origine – ce qui correspond à la limite d’espace d’adressage pour les applications Windows.

Fait intéressant, si vous regardez dans le Gestionnaire des tâches, vous verrez que l’application ne prend presque pas de mémoire du tout. Pourtant, malloc () échoue. C’est ce qui m’amène à croire que l’espace d’adressage virtuel est en train de s’épuiser.

(Une autre expérience à essayer consiste à commenter la réallocation, donc aucune mémoire n’est libérée ou réallouée. L’application échoue exactement au même endroit: après 38 itérations. La seule différence est que cette fois, le Gestionnaire des tâches reflète les 2 Go utilisés.)

Un dernier point d’information: cette même application fonctionne sous Linux. Donc, ce problème realloc () est ssortingctement Windows uniquement.

Des pensées?

Vous fragmentez le tas en faisant cela. Tout ce que vous publiez avec realloc () est ajouté à la liste des blocs libres. Ne plus jamais être utilisé car vous demandez toujours un nouveau bloc plus grand que cela. Ces blocs libres vont simplement accumuler de la mémoire virtuelle jusqu’à ce qu’il ne rest plus rien. Cela arrive assez rapidement lorsque vous jetez près de 50 mégaoctets à la fois.

Vous devrez repenser votre approche.

Après quelques expérimentations et une lecture entre les lignes de la documentation, je suis arrivé à la conclusion que les allocations de mémoire importantes (légèrement inférieures à 512k pour 32 bits, légèrement inférieures à 1Mo pour 64 bits) utilisent un espace d’adresse alloué avec VirtualAlloc et réservé pour ce bloc de mémoire particulier.

S’il n’est pas pratique d’augmenter votre taille de tampon à la volée, comme le suggère Hans, et que vous ne souhaitez pas copier les données, je pense que votre seule autre option est de réserver un bloc d’espace suffisant pour tous vos tampons et d’allouer espace de lui-même. La complexité de cette opération dépend de facteurs tels que la manière dont votre application utilise et libère les tampons, de la possibilité d’écrire plusieurs tampons à la fois et de la quantité de données à traiter pendant la durée de vie de l’application.

Supplémentaire: voici un code de test montrant ce que je vois se produire:

 #include  #include  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; } 

et mes résultats (voir la structure 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 

On peut voir que les petites allocations sont compactées, mais les grosses allocations sont toutes réparties dans des adresses virtuelles distinctes. L’espace libre dans les allocations séparées n’est pas utilisé. En outre, seule l’allocation d’adresse virtuelle principale possède des blocs de segment marqués comme espace libre (indicateur égal à 0 ou 2).