Skip to content

Gestione Avanzata della Memoria Dinamica

L’operatore new delega, in maniera predefinita, al sistema operativo la creazione dello spazio di memoria nel Free Store (l’Heap). Per gran parte delle applicazioni general-purpose, questo va bene. Tuttavia, in software dove i requisiti di Latenza sono estremi (come l’HFT, High Frequency Trading, lo sviluppo di Videogiochi Tripla-A) o in ambiti ristretti dove il sistema operativo non esiste (microcontrollori, kernel), non puoi dipendere dalla misericordia del sistema per concederti la memoria. Devi prenderne il controllo.

Il problema della Frammentazione (Memory Fragmentation)

Section titled “Il problema della Frammentazione (Memory Fragmentation)”

Uno dei problemi cardine è la Frammentazione. Nel tempo, continue operazioni di new e delete lasceranno “buchi” vuoti nello spazio di memoria dell’applicazione. Arriverà il momento in cui avrai abbastanza memoria libera totale per salvare un oggetto massiccio, ma tale memoria libera risulterà distribuita a macchia di leopardo. Il sistema non riuscirà a trovare un “blocco contiguo” gigantesco in cui incastrarlo, provocando il fallimento della richiesta con conseguente crash std::bad_alloc.

Una tecnica comune prevede di ritagliarsi uno spazio abnorme della memoria all’avvio dell’applicazione sotto forma di secchi contigui (Buckets). Ogni volta che viene richiesto un puntatore via new, il nostro codice andrà a cercare il primo secchio non utilizzato e lo segnerà come “occupato”.

Per “bypassare” il sistema operativo, basta implementare una di queste quattro firme della libreria <new> nel proprio codice globale:

  • void* operator new(size_t);
  • void operator delete(void*);
  • void* operator new[](size_t);
  • void operator delete[](void*);

Se le implementi, il C++ cesserà di rivolgersi all’OS e userà solo la tua logica.

Con l’approccio appena visto, distruggiamo la frammentazione: allocare un singolo carattere in memoria (“a”) ci “costerà” l’utilizzo di un intero blocco gigante da 4096 bytes. Barattiamo uno spreco locale in cambio di stabilità globale a prova di proiettile nel tempo.

A volte non vogliamo sovrascrivere l’allocazione ovunque. Vogliamo pre-allocare un array gigante, per poi “plasmare” e inizializzare i nostri oggetti posizionandoli fisicamente in offset predefiniti all’interno di esso, scavalcando la new dinamica. Si usa il Placement New.

La particolarità letale di questa mossa è che, siccome abbiamo ingannato il sistema forzandolo ad allocare in un “vettore preesistente”, non avremo il diritto di chiamare delete (il sistema non saprebbe come comportarsi con un puntatore impiantato artigianalmente). Sarà unicamente e tragicamente nostra responsabilità invocare a mano il distruttore degli oggetti.