La frammentazione della memoria è lo stesso concetto della frammentazione del disco: si riferisce allo spazio sprecato perché le aree in uso non sono abbastanza vicine.
Supponiamo per un semplice esempio di giocattolo che hai dieci byte di memoria:
| | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
Ora assegniamo tre blocchi a tre byte, nome A, B e C:
| A | A | A | B | B | B | C | C | C | |
0 1 2 3 4 5 6 7 8 9
Ora dealloca il blocco B:
| A | A | A | | | | C | C | C | |
0 1 2 3 4 5 6 7 8 9
Ora cosa succede se proviamo ad allocare un blocco a quattro byte D? Bene, abbiamo quattro byte di memoria libera, ma non abbiamo quattro byte contigui di memoria libera, quindi non possiamo allocare D! Questo è un uso inefficiente della memoria, perché avremmo dovuto essere in grado di memorizzare D, ma non siamo stati in grado di farlo. E non possiamo spostare C per fare spazio, perché molto probabilmente alcune variabili nel nostro programma indicano C, e non possiamo trovare e modificare automaticamente tutti questi valori.
Come fai a sapere che è un problema? Bene, il più grande segno è che la dimensione della memoria virtuale del tuo programma è considerevolmente più grande della quantità di memoria che stai effettivamente usando. In un esempio del mondo reale, avresti molti più di dieci byte di memoria, quindi D verrebbe allocato a partire da un byte 9 e i byte 3-5 rimarranno inutilizzati a meno che tu non assegni in seguito qualcosa di tre byte lungo o più piccolo.
In questo esempio, 3 byte non sono molto da sprecare, ma si consideri un caso più patologico in cui due allocazioni di una coppia di byte sono, ad esempio, distanti dieci megabyte in memoria ed è necessario allocare un blocco di dimensioni 10 megabyte + 1 byte. Devi andare a chiedere al sistema operativo più di dieci megabyte di memoria virtuale per farlo, anche se hai solo un byte di spazio in meno.
Come lo previeni? I casi peggiori tendono a manifestarsi quando si creano e distruggono frequentemente piccoli oggetti, poiché ciò tende a produrre un effetto "formaggio svizzero" con molti piccoli oggetti separati da molti piccoli fori, rendendo impossibile allocare oggetti più grandi in quei fori. Quando sai che lo farai, una strategia efficace è quella di pre-allocare un grande blocco di memoria come pool per i tuoi piccoli oggetti e quindi gestire manualmente la creazione di piccoli oggetti all'interno di quel blocco, piuttosto che lasciarlo l'allocatore predefinito lo gestisce.
In generale, minore è il numero di allocazioni eseguite, meno probabile è la frammentazione della memoria. Tuttavia, STL si occupa di questo piuttosto efficacemente. Se si dispone di una stringa che utilizza l'intera allocazione corrente e si aggiunge un carattere ad essa, non si riassegna semplicemente alla sua lunghezza corrente più una, ma raddoppia la lunghezza. Questa è una variazione della strategia "pool per piccole allocazioni frequenti". La stringa sta afferrando un grosso pezzo di memoria in modo che possa gestire in modo efficiente piccoli ripetuti aumenti di dimensioni senza effettuare piccole riallocazioni ripetute. Tutti i contenitori STL in effetti fanno questo genere di cose, quindi generalmente non dovrai preoccuparti troppo della frammentazione causata dalla riallocazione automatica dei contenitori STL.
Anche se ovviamente i contenitori STL non mettono in comune la memoria tra loro, quindi se hai intenzione di creare molti piccoli contenitori (anziché alcuni contenitori che vengono ridimensionati frequentemente) potresti doverti preoccupare di prevenire la frammentazione nello stesso modo in cui sarebbe per qualsiasi piccolo oggetto creato di frequente, STL o no.