STACK E HEAP
Nei value e reference types in C Sharp quando un programma C# va in esecuzione l’ambiente di runtime crea due aree di memoria ben distinte, lo stack e lo heap. Iniziamo a parlare dello stack aiutandoci con il programma sotto riportato e con la schematizzazione in figura.
IL PROGRAMMA PER ANALIZZARE LO STACK
using System; namespace StackAndHeap { class Program { static void Main() { int x = 10; int result=FirstMethod(x); } static int FirstMethod(int a) { int y = 20; int z; z = SecondMethod(y); return z+a; } static int SecondMethod(int b) { int k = 30; return b+k; } } }
Lo stack (pila in italiano) è un’area di memoria molto veloce, una struttura di tipo LIFO (Last In First Out) cioè l’ultimo valore immagazzinato è il primo ad uscire. Lo stack oltre al program counter tiene traccia del flusso di esecuzione del programma. Ma andiamo per ordine, abbiamo visto nei post precedenti che in una applicazione console il metodo Main è il primo ad essere eseguito. Quando il programma entra in questo metodo nello stack viene creato un cosiddetto frame che memorizza tutti i parametri del metodo (per semplicità ho omesso l’argomento del metodo Main, cioè l’array di stringhe) cioè le variabili locali x e result di figura 3.
LA CHIAMATA AI METODI
Quando arriva la call al FirstMethod l’esecuzione del programma salta a questo metodo. Sullo stack viene creato un nuovo frame in cui vengono immagazzinati il parametro di input a e le due variabili locali y e z. Il frame appena creato occupa la cima dello stack Figura 2. Quando viene chiamato SecondMethod viene creato nuovamente un frame che memorizza tutti parametri e variabili di SecondMethod Figura 3 e l’esecuzione prosegue con l’esecuzione di quest’ultimo. Al termine di SecondMethod si risale la cima dello stack in particolare tutte i parametri e le variabili di questo metodo vengono cancellati dalla memoria e si passa di nuovo ad eseguire FirstMethod e così via fino all’ultima istruzione del metodo Main che termina il programma (Figura 4).
LO HEAP
L’Heap (catasta mucchio in italiano) a differenza dello stack è un’area di memoria in cui non viene tracciata l’esecuzione del programma, questo è compito dello stack ma è un’area di memoria disordinata in cui vengono memorizzati gli oggetti per un certo tempo. È compito del Garbage Collector quando non ci sono più riferimenti ad un oggetto liberare la memoria, ma questo lo vedremo meglio parlando dei Value Types e Reference Types.
TIPI VALORE E TIPI RIFERIMENTO
In C# tutti i tipi sia quelli visti sinora sia quelli che vedremo più avanti come le classi, le struct e le enumerazioni si dividono in due grossi blocchi: I tipi che vengono gestiti a valore, come i tipi primitivi già visti, e i tipi riferimento come le stringhe. Supponiamo per comprendere la differenza tra i due tipi che nel metodo Main abbiamo dichiarato due variabili:
int x = 234;
string s = “pippo”;
Vediamo con una figura come l’ambiente di runtime gestisce il tipo int (Value Types) e il tipo string (Reference Types).
Per quanto riguarda il comportamento dei value types come ad esempio un int vediamo che nello stack oltre alla variabile x viene memorizzato direttamente il valore 234 ossia la variabile x contiene o per meglio dire punta direttamente il valore 234. Per quanto riguarda invece le stringhe che abbiamo già incontrato, nello stack viene memorizzata la variabile e il riferimento alla stringa ossia nel reference è contenuta la locazione di memoria che si trova nello heap e che contiene il valore “pippo”. Questa è la grossa differenza tra un value types che memorizzano direttamente il valore insieme alla variabile nello stack e i reference types che nello stack o meglio nel frame creato dal runtime memorizzano una variabile che punta il riferimento all’oggetto memorizzato sullo Heap.
SEMANTICA DEI VALUE TYPES
Supponiamo che il nostro programma esegua il metodo Main nel quale abbiamo dichiarato due variabili x e y di tipo intero e assegniamo il valore di x ad y. Il runtime come sai crea un frame nello stack e all’interno del frame memorizza le variabili x e y insieme ai loro valori.
int x = 120;
int y = 50;
y=x;
x=5;
vediamo con una figura la situazione che si crea nello stack.
Quando il flusso del programma nel Main assegna il valore di x ad y vengono create due copie separate per il valore literal 120 una per x e l’altra per y. Quando riassegniamo il valore ad x (x=5) si vede che y mantiene il precedente valore e x assume 5. Ciò sta a dimostrare che per i value types le due variabili sono completamente slegate.
SEMANTICA DEI REFERENCE TYPES
Supponiamo che nel nostro metodo Main dichiariamo un array di int, gli array li vedremo successivamente. Un array è una collezione di oggetti di un certo tipo la sintassi per dichiarare un array è riportata più in basso. Poi supponiamo di creare un secondo array y ed assegnarlo a x; Infine modifichiamo il primo elemento di x. Vediamo con una figura come si presenta la situazione in memoria.
int [] x = new [] {30,40};
int [] y = x;
x[0] = 10;
PASSAGGIO DEI PARAMETRI PER VALORE
PASSAGGIO DEI PARAMETRI PER RIFERIMENTO
LA PAROLA CHIAVE NULL
La keyword null rimuove il riferimento dallo stack che conteneva la locazione di memoria dell’oggetto puntato sull’heap. Se l’oggetto rimane orfano sull’heap, cioè non possiede più riferimenti attivi esiste un meccanismo in C# chiamato garbage collector che esegue una scansione e una pulizia di tutti gli oggetti rimasti orfani nello heap. Se non ci fosse questo meccanismo che è automatico ben presto si arriverebbe ad un memory leak. Vediamo di chiarire il concetto con una immagine.
APPROFONDIMENTO
In C#, i tipi di valore e i tipi di riferimento sono due categorie fondamentali che determinano il comportamento delle variabili e la gestione della memoria. Vediamo le differenze principali e gli esempi di ciascun tipo.
TIPI DI VALORE
I tipi di valore contengono direttamente i loro dati. Quando una variabile di tipo di valore viene assegnata a un’altra variabile, viene creata una copia dei dati. I tipi di valore vengono allocati nello stack, il che generalmente offre un accesso più rapido ma una vita utile limitata al contesto di esecuzione.
ESEMPI DI TIPI DI VALORE
• Tipi Primitivi: int, char, float, double, bool
•Strutture: struct
•Enumerazioni: enum
CARATTERISTICHE DEI TIPI DI VALORE
•Allocati nello stack
•Contengono direttamente i dati
•Copia dei dati durante l’assegnazione
•Non possono essere nulli (a meno che non siano dichiarati come nullable con ?)
Esempio
int a = 10;
int b = a; // b è una copia di a, quindi b = 10
b = 20; // Modificando b non si modifica a
Console.WriteLine(a); // Output: 10
Console.WriteLine(b); // Output: 20
TIPI DI RIFERIMENTO
I tipi di riferimento contengono un riferimento ai dati. Quando una variabile di tipo di riferimento viene assegnata a un’altra variabile, entrambe le variabili fanno riferimento agli stessi dati. I tipi di riferimento vengono allocati nell’heap, il che può portare a una gestione della memoria più complessa ma offre una vita utile estesa fino a quando non esiste più alcun riferimento ai dati.
ESEMPI DI TIPI DI RIFERIMENTO
•Classi: class
•Interfacce: interface
•Delegati: delegate
•Stringhe: string
•Array
CARATTERISTICHE DEI TIPI DI RIFERIMENTO
•Allocati nell’heap
•Contengono un riferimento ai dati
•Condivisione dei dati durante l’assegnazione
•Possono essere nulli
Esempio
class Person
{
public string Name { get; set; }
}
Person person1 = new Person { Name = “Alice” };
Person person2 = person1; // person2 fa riferimento agli stessi dati di person1
person2.Name = “Bob“; // Modificando person2 si modifica anche person1
Console.WriteLine(person1.Name); // Output: Bob
Console.WriteLine(person2.Name); // Output: Bob
Utilizzare i tipi di valore per dati piccoli e immutabili che hanno un breve ciclo di vita.
Utilizzare i tipi di riferimento per oggetti complessi e dati che richiedono un ciclo di vita più lungo e manipolazioni più complesse.
Questi concetti sono fondamentali per comprendere la gestione della memoria, l’efficienza e il comportamento delle applicazioni C#.
Lascia un commento