Segmenti di memoria
La memoria assegnata ad un certo processo è divisa in segmenti, ovvero porzioni di memoria ognuna con un compito specifico. Di seguito i più importanti.
Indirizzo | Settore |
---|---|
0x00 | Codice |
... | Variabili Statiche |
... | Heap |
... | |
0x7FFFFFFFFFFF | Stack |
Codice
La prima porzione di memoria contiene le istruzioni che dovranno essere eseguite durante il processo; questo settore viene chiamato code o anche text. Le istruzioni vengono memorizzate in memoria in accordo con l'architettura specifica del processore, e possono essere visualizzate in maniera comprensibile all'essere umano attraverso la rappresentazione Assembly.
Prendiamo ad esempio una funzione in C che esegue la somma di due numeri. Di seguito vediamo come questo viene tradotto in Assembly, e come ad ogni riga di Assembly corrispondano dei valori esadecimali. Questi valori sono quelli che si trovano nel segmento di memoria del codice.
Variabili statiche
Subito dopo vengono memorizzate tutte quelle variabili che nascono con l'avvio del programma e vengono distrutte solo alla conclusione dello stesso. Rientrano in questa categoria ad esempio tutte le variabili globali e le variabili locali che vengono dichiarate con il modificatore static.
Stack & Heap
I settori precedenti hanno una dimensione fissa, nota all'avvio del programma. Tuttavia, durante l'esecuzione del programma stesso, le variabili che creiamo all'interno delle funzioni non sono note a priori, perché una funzione potrebbe essere chiamata più volte, oppure mai.
Esistono quindi due ulteriori settori che evolvono con l'esecuzione del programma stesso, chiamate stack (catasta, a sinistra nella foto) e heap (mucchio, a destra nella foto).
Stack
La stack è una memoria a cui si può aggiungere una nuova variabile solo aggiungendola in cima, ed analogamente si possono togliere variabili solo dalla cima. Questa memoria viene gestita automaticamente dal compilatore e dal processore: ogni volta che creiamo una variabile, viene aggiunta alla stack; ogni volta che usciamo dall'ambito della variabile (in inglese, scope), questa variabile viene distrutta. Si esce dallo scope della variabile appena si raggiunge la parentesi graffa in cui è contenuta.
int somma(int a, int b) {
if (a > 0) {
int c = a+b;
return c;
} // <-- qui viene distrutta la variabile c
} // qui vengono distrutte le variabili a e b
Per convenzione, la stack parte nello spazio di indirizzamento più alto disponibile della memoria virtuale e cresce verso gli indirizzi più bassi.
Heap
La gestione automatica della stack a volte può essere limitante, perché non sempre lo scope reale della variabile corrisponde con quello del blocco in cui si trova. Per questo motivo esiste la heap, dove si ha maggiore controllo su quando distruggere la variabile, che può quindi passare da un blocco ad un altro (ad esempio può essere passato facilmente ad una o più funzioni).
Se non si cancellano correttamente le variabili nella heap, può succedere che aree di memoria vengano sprecate. Questo fenomeno si chiama memory leak ed è un problema a cui bisogna prestare la massima attenzione. Alcuni linguaggi come Java, Python o Rust hanno dei meccanismi per evitare che si verifichino i memory leaks.