0b1d3e024d0b2fbba7211763a82ce06103c0fc4d
linguaggi/s02/20260214.md
| ... | ... | @@ -1,277 +0,0 @@ |
| 1 | -# Gestione della memoria (da slide 7) |
|
| 2 | - |
|
| 3 | -Allocazione dinamica (pila) |
|
| 4 | - |
|
| 5 | -con la ricorsione l'allocazione statica non basta (a runtime possono esistere piu istanze della stessa variabile locale di una procedura) |
|
| 6 | -ogni istanza di un sottoprogramma a run time ha una porzione di memoria detta **record di attivazione** (o frame) contenente le informazioni relative alla specifica istanza (indirizzo di ritorno) |
|
| 7 | -Analogalmente ogni blocco ha un suo record di attivazione (piu semplice) |
|
| 8 | -La pila (lifo) e' la struttura e' la struttura dati naturale per gestire i recordd did attivazione perche le chiamate di procedura ed i blocchi sono annidati uno dentro laltro |
|
| 9 | - |
|
| 10 | -La pila puo essere usata anche in un linguaggio senza ricorsione |
|
| 11 | - |
|
| 12 | -Record di attivazione per blocchi anonimi |
|
| 13 | - |
|
| 14 | - |
|
| 15 | - |
|
| 16 | -Allocazione dinamica con pila |
|
| 17 | - |
|
| 18 | -La gestione della pila e' compiuta mediante: |
|
| 19 | - |
|
| 20 | -- sequenza di chiamata (codice eseguito dal chiamante immediatamente prima della chiamata) |
|
| 21 | -- prologo (codice eseguito all'inizio del blocco) |
|
| 22 | -- epilogo (codice eseguito alla fine del blocco) |
|
| 23 | -- sequenza di ritorno (codice eseguito dal chiamante immediatamente dopo la chiamata) |
|
| 24 | - |
|
| 25 | -Indirizzo di un RdA non e' noto a compile-time |
|
| 26 | -Il puntatore RdA o SP punta al RdA del blocco attiva |
|
| 27 | -Le info contenute in un RdA sono accessibili per offset rispetto allo SP (indirizzo-info = contenuto(SP)+offset) |
|
| 28 | -offset determinabile staticamente dal compilatore |
|
| 29 | -Somma SP+offset eseguita con unica istruzione macchina load o store |
|
| 30 | - |
|
| 31 | -Record di attivazione per blocchi in-line |
|
| 32 | -Link dinamico (o control link) puntatore al precedente record sullo stack |
|
| 33 | -ingresso nel blocco: push (link dinamico del nuovo Rda := SP – SP aggiornato a nuovo RdA) |
|
| 34 | -Uscita dal blocco: Pop |
|
| 35 | -– Elimina RdA puntato da SP |
|
| 36 | -– SP := link dinamico del Rda tolto dallo stack |
|
| 37 | - |
|
| 38 | - |
|
| 39 | -In realtà… |
|
| 40 | -• In molti linguaggi non c’è manipolazione |
|
| 41 | -della pila per i blocchi anonimi ! |
|
| 42 | -• Tutte le dichiarazioni dei blocchi annidati |
|
| 43 | -sono raccolte dal compilatore |
|
| 44 | -• Allocazione di spazio per tutte |
|
| 45 | -• Potenziale spreco di memoria, ma… |
|
| 46 | -• Nessuna perdita di efficienza per la |
|
| 47 | -gestione della pila |
|
| 48 | - |
|
| 49 | -Record di attivazione per procedure |
|
| 50 | - |
|
| 51 | - |
|
| 52 | -Gestione della pila: |
|
| 53 | - |
|
| 54 | - |
|
| 55 | -Esempio |
|
| 56 | - |
|
| 57 | - |
|
| 58 | - |
|
| 59 | - |
|
| 60 | -non e' possibile determinare prima il numero massimo di record di attivazione |
|
| 61 | -Se la chiamata ricorsiva e' lultima cosa che viene fatta es. else return fact(n-1) non devo generare un nuovo record di attivazione. Si chiama ricorsione in coda. Posso usare un solo record did attivazione per la ricorsione in coda. e'possibilie trasformare una chiamata ricorsiva in una ricorsiva con ricorsione in coda (spesso aggiungendo un paramentro accumulatore che salvi il risultato) |
|
| 62 | -La complessita in termini di spazio e' lineare con n (fib e' esponenziale) |
|
| 63 | - |
|
| 64 | -Gestione pila: ingresso in blocco |
|
| 65 | - |
|
| 66 | -- modifica contatore programma |
|
| 67 | -- allocazione RdA sulla pila (modifica puntatore a top (visto che e' una pila il top e' lultimo elemento)) |
|
| 68 | -- modifica del putatore al TdA |
|
| 69 | -- passaggio dei parametri |
|
| 70 | -- salvataggio dei registri |
|
| 71 | -- eventuali inizializzazioni |
|
| 72 | -- trasferimento del controllo |
|
| 73 | - |
|
| 74 | -Gestione della pila: uscita da blocco |
|
| 75 | -• Sequenza di uscita ed epilogo si dividono i seguenti compiti: |
|
| 76 | -– Restituzione dei valori dal chiamato al chiamante, oppure il |
|
| 77 | -valore calcolato dalla funzione |
|
| 78 | -– Ripristino dei registri |
|
| 79 | -• In particolare deve essere ripristinato il vecchio valore del |
|
| 80 | -puntatore al RdA. |
|
| 81 | -– Eventuale finalizzazione |
|
| 82 | -– Deallocazione dello spazio sulla pila |
|
| 83 | -– Ripristino del valore del contatore programma |
|
| 84 | - |
|
| 85 | -Allocazione dinamica con heap |
|
| 86 | - |
|
| 87 | -Heap: regione di memoria i cui sotto blocchi possono essere allocati e deallocati in momenti arbitrari |
|
| 88 | -Necessario quando il linguaggio permette allocazione esplicita di memoria a run time (puntatotri e strutture dati dinamiche tipo alberi e liste) |
|
| 89 | -Oggetti di dimensione variabili (stringhe, insiemi) |
|
| 90 | -Oggetti la cui vita non ha un regime definito a priori (cioe' con vita non lifo) |
|
| 91 | -La gestione dell heap non e'banale (gestione efficiente dello spazio: frammentazione e Velocita' di accesso) |
|
| 92 | - |
|
| 93 | -Heap: blocchi di dimensione fissa |
|
| 94 | -Heap suddiviso in blocchi di dimensione fissa (e abbastanza limitata: qualche parola) |
|
| 95 | -In origine: tutti i blocchi collegati nella lista libera |
|
| 96 | - |
|
| 97 | - |
|
| 98 | -Heap: blocchi di dimensione fissa |
|
| 99 | -• Allocazione di uno o più blocchi contigui |
|
| 100 | -• Deallocazione: restituzione alla lista liber |
|
| 101 | - |
|
| 102 | -Problema: frammentazione della memoria |
|
| 103 | - |
|
| 104 | -Heap: blocchi di dimensione variabile |
|
| 105 | -Inizialmente unico blocco nello heap |
|
| 106 | -Allocazione: determinazione di un blocco libero della dimensione opportuna |
|
| 107 | -Deallocazione: restituzione alla lista libera |
|
| 108 | - |
|
| 109 | -Problemi: |
|
| 110 | - |
|
| 111 | -- le operazioni devono essere efficienti |
|
| 112 | -- evitare lo spreco di memoria (frammentazione interma e esterna) |
|
| 113 | - |
|
| 114 | -Frammentazione |
|
| 115 | -Interna: lo spazio richiesto e' x: - viene allocato un blocco di dimensione y dove y>x - spreco dello spazio |
|
| 116 | -Esterna: |
|
| 117 | - |
|
| 118 | -- ci sarebbe lo spazio necessario ma e' inusabile perche diviso in pezzi troppo piccoli |
|
| 119 | - La memoria deve essere contigua (per accedere in modo efficiente ad un array lo si fa tramite offset) |
|
| 120 | - |
|
| 121 | - |
|
| 122 | -Cosa posso fare? Spostare tutti gli indirizzi e ricompattare il tutto? molto costoso |
|
| 123 | - |
|
| 124 | -Gestione della lista libera: unica lista |
|
| 125 | -Inizialmente contiene un solo blocco, della dimensione dello heap |
|
| 126 | -Ad ogni richiesta di allocazione:cerca blocco di dimensione opportuna, due modi di ricerca |
|
| 127 | - |
|
| 128 | -- first fit: primo blocco grande abbastanza (molto veloce) |
|
| 129 | -- best fit: quello di dimensione più piccola, grande abbastanza (molto efficiente) |
|
| 130 | - |
|
| 131 | -Se il blocco scelto è molto più grande di quello che serve, viene diviso in due e la parte inutilizzata è aggiunta alla LL |
|
| 132 | -• Quando un blocco è de-allocato, viene restitutito alla LL (se un blocco adiacente è libero, i due blocchi sono ``fusi’’ in un unico blocco). |
|
| 133 | - |
|
| 134 | -Liste libere multiple |
|
| 135 | -Per blocchi di dimensione diversa (la ripartizione dei blocchi fra le varie liste puo essere statica o dinamica (buddy system o fibonacci system)) |
|
| 136 | -Buddy system: k liste; la lista k ha blocchi di dimensione $2^k$ |
|
| 137 | - |
|
| 138 | -- se richiesta allocazione per blocco di $2^k$ è tale dimensione non è disponibile, blocco di 2k+1 diviso in 2 |
|
| 139 | -- se un blocco di 2k e’ de-allocato è riunito alla sua altra metà (buddy), se disponibile |
|
| 140 | - |
|
| 141 | -Fibonacci simile, ma si usano numeri di Fibonacci invece di potenze di 2 (crescono più lentamente) |
|
| 142 | - |
|
| 143 | -Implementazione delle regole di scope |
|
| 144 | -• Scope statico |
|
| 145 | -– catena statica |
|
| 146 | -– display |
|
| 147 | -• Scope dinamico |
|
| 148 | -– A-list |
|
| 149 | -– Tabella centrale dell’ambiente (CRT) |
|
| 150 | - |
|
| 151 | -Come si determina il legame corretto? |
|
| 152 | - |
|
| 153 | -``` |
|
| 154 | -{int x=10; |
|
| 155 | -void foo () { |
|
| 156 | -x++; |
|
| 157 | -} |
|
| 158 | -void fie (){ |
|
| 159 | -int x=0; |
|
| 160 | -foo(); |
|
| 161 | -} |
|
| 162 | -fie(); |
|
| 163 | -foo(); |
|
| 164 | -} |
|
| 165 | -``` |
|
| 166 | - |
|
| 167 | -Il codice di foo deve accedere sempre alla stessa |
|
| 168 | -variabile x |
|
| 169 | -• Tale x è memorizzato in un certo RdA (in questo caso in |
|
| 170 | -quello del main) |
|
| 171 | -• In cima alla pila abbiamo il RdA di foo (perché foo è in |
|
| 172 | -esecuzione) |
|
| 173 | - |
|
| 174 | -- Determina prima il corretto RdA dove trovare x |
|
| 175 | - •Accedi a x tramite offset relativo a tale RdA (e non relativo a SP) |
|
| 176 | -  |
|
| 177 | - |
|
| 178 | -Record di attivazione per scoping statico |
|
| 179 | - |
|
| 180 | -Link dinamico: |
|
| 181 | -– puntatore all’RdA precedente sulla pila (RdA del chiamante) |
|
| 182 | -• Link statico: |
|
| 183 | -– puntatore all’RdA del blocco che contiene immediatamente il testo del blocco in esecuzione |
|
| 184 | -• Osserva: |
|
| 185 | -– link dinamico dipende dalla sequenza di esecuzione del programma |
|
| 186 | -– link statico dipende dall’annidamento statico (nel testo) delle dichiarazioni delle procedure |
|
| 187 | - |
|
| 188 | -Catena Statica: esempio |
|
| 189 | -Sequenza di chiamate a run time A, B, C, D, E, C |
|
| 190 | - |
|
| 191 | -le linee tratteggiate sono link statici |
|
| 192 | - |
|
| 193 | - |
|
| 194 | -Se un sottoprogramma è annidato a livello k, allora la catena è lunga k |
|
| 195 | - |
|
| 196 | -se sono in e e sto cercando una var x non locale allora vado in c e poi vado in a |
|
| 197 | -questo grazie ai link statici (questi puntatori sono determinati a runtime) |
|
| 198 | -Esempio |
|
| 199 | - |
|
| 200 | -``` |
|
| 201 | -{int x; |
|
| 202 | -void A(){ |
|
| 203 | - x=x+1;} |
|
| 204 | -void B(){ |
|
| 205 | - int x; |
|
| 206 | - void C (int y){ |
|
| 207 | - int x; |
|
| 208 | - x=y+2; A(); |
|
| 209 | - } |
|
| 210 | - x=0; A(); C(3); |
|
| 211 | -} |
|
| 212 | -x=10; |
|
| 213 | -B(); |
|
| 214 | -} |
|
| 215 | -``` |
|
| 216 | - |
|
| 217 | -Struttura main con dentro a e b e b con dentro c |
|
| 218 | - |
|
| 219 | - |
|
| 220 | -la x che viene modificata e' sempre quella del main visto che viene modificata da A e il puntatore di catena statica di A punta al main. Le altre x non vengono modificate da A. |
|
| 221 | -C modifica la propria x (visto che la dichiara ed e'quindi locale) |
|
| 222 | - |
|
| 223 | -il compilatore dice di risalire di 1 il record di attivazione. A lo fa e trova a x del main (con un offset) |
|
| 224 | - |
|
| 225 | -se in C avessi la variabile pippo chee e' definita nel main il compilatore mi direbbe che pippo e' definito in main (2 livelli sopra) e quindi quando devo manipolarla o chiamarla salgo due livelli e ne uso il valore |
|
| 226 | - |
|
| 227 | -Il compilatore sa dove sono dichiarate le variabili ma non sa la loro posizione a runtime (quindi ci dice solo quanto andare in su ma il dove va deciso a runtime grazie alla catena statica) |
|
| 228 | - |
|
| 229 | -Dal punto di vista del supporto a run time |
|
| 230 | -Come viene determinato il link statico del chiamato? |
|
| 231 | - |
|
| 232 | -es sopra |
|
| 233 | -sono nel main, chiamo B devo inizializzre il puntatore di catena statica di B, so che B e' inizializzato dentro al main quindi inizializzo il suo puntatore al main (me) |
|
| 234 | -Ora B chiama A, come posso inizializzare il suo puntatore? B sa che A e' allo stesso livello di annidamento di A e quindi gli basta risalire di (0) livelli e passare il puntatore a A. |
|
| 235 | -In generale o X e' inizializzato dentro a A (e quindi il suo indirizzo e' A) oppure Si calcola livello di annidamento di A - X e si risalgono i A-X livelli e si assegna l'indirizzo a X |
|
| 236 | - |
|
| 237 | -e'il chiamante a determinare il link statico del chiamato |
|
| 238 | -Info a disposizione del chiamante: |
|
| 239 | - |
|
| 240 | -- annidamento statico dei blocchi (determinata dal compilatore) |
|
| 241 | -- proprio RdA |
|
| 242 | - |
|
| 243 | -Come determinare il puntatore di Catena statica (CS) |
|
| 244 | -il chiamante Ch conosce l'annidamento dei blocchi: |
|
| 245 | - |
|
| 246 | -- quando Ch chiama P sa se la definizione di P e': |
|
| 247 | - - immediatamente inclusain Ch (k=0) |
|
| 248 | - - in un blocco k passi fuori da Ch |
|
| 249 | - |
|
| 250 | -– nessun altro caso possibile: |
|
| 251 | -• perché P deve essere in scope! |
|
| 252 | -– nel caso a destra: |
|
| 253 | -• chiamate: A, B, C, D, E, C |
|
| 254 | -– con i dati di catena statica: |
|
| 255 | -• A; (B,0); (C,1); (D,0); (E,1); (C,2) |
|
| 256 | - |
|
| 257 | -Se k=0: |
|
| 258 | -– Ch passa a P il proprio SP |
|
| 259 | -•Se k>0: |
|
| 260 | -– Ch risale la propria catena statica di k passi e passa il puntatore così determinato |
|
| 261 | - |
|
| 262 | - |
|
| 263 | -> Nota: Se B chiamasse D non potrebbe farlo perche' non lo puo' vedere. |
|
| 264 | - |
|
| 265 | -Ripartizione dei compiti |
|
| 266 | -Compilatore: |
|
| 267 | - |
|
| 268 | -- associa l'informazione k ad ogni chiamata |
|
| 269 | -- associa ad ogni nome un indice h: |
|
| 270 | - - h=0: nome locale |
|
| 271 | - - h diverso da 0: nome non locale definito h blocchi sopra |
|
| 272 | -- sequenza chiamata/prologo |
|
| 273 | - - risale la catena statica |
|
| 274 | - - inizializza il puntatore di catena statica |
|
| 275 | -- Costi: |
|
| 276 | - - per ogni chiamata: k passi di catena statica |
|
| 277 | - - ad ogni accesso ad una variabile non locale:(h passi di catena statica in piu rispetto all'accesso ad un locale) |
linguaggi/s02/20260224.md
| ... | ... | @@ -0,0 +1,305 @@ |
| 1 | +## Gestione della Memoria: Allocazione Dinamica e Pila (Stack) |
|
| 2 | + |
|
| 3 | +Slide allocazione memoria lucido #7 |
|
| 4 | + |
|
| 5 | +### Perché l'Allocazione Dinamica? |
|
| 6 | + |
|
| 7 | +L'allocazione statica della memoria non è sufficiente nei linguaggi moderni, specialmente quando si usa la **ricorsione**. A tempo di esecuzione (run-time), possono esistere contemporaneamente più istanze della stessa variabile locale di una procedura. L'allocazione dinamica risolve questo problema.con la ricorsione l'allocazione statica non basta (a runtime possono esistere piu istanze della stessa variabile locale di una procedura) |
|
| 8 | + |
|
| 9 | +### Il Record di Attivazione (RdA) |
|
| 10 | + |
|
| 11 | +Ogni volta che un sottoprogramma viene chiamato a run-time, gli viene assegnata una porzione di memoria chiamata **Record di Attivazione** (o _Frame_). |
|
| 12 | +L'RdA contiene tutte le informazioni relative a quella specifica istanza (variabili locali, indirizzo di ritorno, ecc.). Anche i blocchi di codice anonimi (come quelli nei cicli o negli `if`) hanno un loro RdA, seppur più semplice. |
|
| 13 | + |
|
| 14 | +La struttura base di un RdA per blocchi anonimi include: |
|
| 15 | + |
|
| 16 | +- **Puntatore di Catena Dinamica:** Punta all'RdA del blocco o sottoprogramma chiamante. |
|
| 17 | +- **Variabili Locali:** Lo spazio per le variabili dichiarate in quel blocco. |
|
| 18 | +- **Risultati Intermedi:** Spazio temporaneo per i calcoli. |
|
| 19 | + |
|
| 20 | + |
|
| 21 | +La Pila (LIFO - Last In, First Out) è la struttura dati naturale per gestire i Record di Attivazione. Questo perché le chiamate di procedura e i blocchi di codice sono sempre annidati uno dentro l'altro, seguendo esattamente la logica LIFO. |
|
| 22 | + |
|
| 23 | +> **Nota:** La pila viene utilizzata per l'allocazione dinamica anche in linguaggi che non supportano la ricorsione. |
|
| 24 | +> Record di attivazione per blocchi anonimi |
|
| 25 | + |
|
| 26 | +### Fasi di Gestione della Pila |
|
| 27 | + |
|
| 28 | +La creazione e distruzione di un RdA è gestita attraverso blocchi di codice specifici: |
|
| 29 | + |
|
| 30 | +- **Sequenza di chiamata:** Codice eseguito dal _chiamante_ subito prima di invocare il sottoprogramma. |
|
| 31 | +- **Prologo:** Codice eseguito all'inizio del blocco invocato (es. prepara l'RdA). |
|
| 32 | +- **Epilogo:** Codice eseguito alla fine del blocco invocato (es. pulisce l'RdA). |
|
| 33 | +- **Sequenza di ritorno:** Codice eseguito dal _chiamante_ subito dopo che la chiamata è terminata. |
|
| 34 | + |
|
| 35 | +### Indirizzamento tramite Stack Pointer (SP) |
|
| 36 | + |
|
| 37 | +Poiché l'indirizzo esatto di un RdA non è noto a tempo di compilazione (compile-time), si utilizza un registro chiamato **Stack Pointer (SP)** (o Puntatore all'RdA), che punta sempre all'RdA del blocco attualmente attivo. |
|
| 38 | + |
|
| 39 | +- Le informazioni dentro l'RdA si leggono tramite un **offset** rispetto all'SP. |
|
| 40 | +- Formula: `Indirizzo del dato = contenuto(SP) + offset` (guarda la foto dello stack) |
|
| 41 | +- L'offset è determinabile staticamente dal compilatore. |
|
| 42 | +- Il calcolo `SP + offset` viene eseguito in modo efficiente con una singola istruzione macchina (load o store). |
|
| 43 | + |
|
| 44 | +### Blocchi In-line e Catena Dinamica |
|
| 45 | + |
|
| 46 | +Nei blocchi annidati (es. un blocco di codice dentro un altro), si utilizza il **Link Dinamico** (o control link), che è un puntatore al precedente record sullo stack. |
|
| 47 | + |
|
| 48 | +- **Ingresso nel blocco:** Si fa un _Push_ (il link dinamico del nuovo RdA diventa l'attuale SP, e poi l'SP viene aggiornato al nuovo RdA). |
|
| 49 | +- **Uscita dal blocco:** Si fa un _Pop_ (l'RdA viene eliminato e l'SP torna a puntare al valore salvato nel link dinamico). |
|
| 50 | +- _Problema:_ In prima approssimazione, in un blocco interno, per accedere alle variabili del blocco esterno (non locali), bisogna "risalire" la catena dinamica seguendo i puntatori, poiché non sono raggiungibili con un semplice `SP + offset`. |
|
| 51 | + |
|
| 52 | + |
|
| 53 | + |
|
| 54 | +> **Nota**: Anche se la teoria prevede un RdA per ogni blocco anonimo, **nella pratica molti linguaggi non manipolano la pila per i blocchi anonimi**. |
|
| 55 | +> |
|
| 56 | +> - Il compilatore raccoglie in anticipo tutte le dichiarazioni dei blocchi annidati di una procedura. |
|
| 57 | +> - Alloca lo spazio per _tutte_ queste variabili in un unico grande RdA all'inizio della procedura. |
|
| 58 | +> - Questo comporta un potenziale spreco di memoria (viene allocata memoria per variabili di blocchi che magari non verranno eseguiti), ma garantisce che **non ci sia alcuna perdita di efficienza** a run-time dovuta ai continui Push e Pop sulla gestione della pila. |
|
| 59 | + |
|
| 60 | +### Il Record di Attivazione (RdA) per le Procedure |
|
| 61 | + |
|
| 62 | +A differenza dei semplici blocchi anonimi, il Record di Attivazione per una procedura (o funzione) è più complesso e strutturato, in quanto deve gestire il passaggio di parametri e il ritorno di valori. I campi tipici (dall'alto verso il basso della pila) sono: |
|
| 63 | + |
|
| 64 | +- **Puntatore di Catena Dinamica:** Punta all'RdA del chiamante (chi ha invocato la procedura). |
|
| 65 | +- **Puntatore di Catena Statica:** Usato per l'accesso alle variabili non locali in linguaggi con scoping statico annidato. |
|
| 66 | +- **Indirizzo di Ritorno:** L'istruzione esatta a cui il programma deve tornare una volta terminata la procedura. |
|
| 67 | +- **Indirizzo del Risultato:** Puntatore alla locazione di memoria (nell'RdA del chiamante) dove verrà salvato il valore calcolato dalla funzione. |
|
| 68 | +- **Parametri:** I valori passati alla procedura al momento della chiamata. |
|
| 69 | +- **Variabili Locali:** Spazio per le variabili dichiarate all'interno della procedura. |
|
| 70 | +- **Risultati Intermedi:** Spazio di lavoro temporaneo (es. per salvare il risultato di una sotto-espressione o di una chiamata ricorsiva prima di completare un calcolo). |
|
| 71 | + |
|
| 72 | + |
|
| 73 | + |
|
| 74 | +Gestione della pila: |
|
| 75 | + |
|
| 76 | +### Struttura della Pila e Puntatori |
|
| 77 | + |
|
| 78 | +La gestione fisica della memoria sulla pila utilizza dei puntatori chiave per orientarsi: |
|
| 79 | + |
|
| 80 | +- **Puntatore RdA (Frame Pointer / Base Pointer):** Punta alla base dell'RdA attualmente in esecuzione. È il punto di riferimento (tramite _offset_) per trovare variabili locali e parametri. |
|
| 81 | +- **Puntatore al Top della Pila (Stack Pointer - SP):** Punta all'ultima locazione di memoria occupata sulla pila. Segna il confine con la zona di memoria ancora libera. |
|
| 82 | +- **Inizio della pila:** L'indirizzo fisso da cui la pila inizia a crescere. |
|
| 83 | +  |
|
| 84 | + |
|
| 85 | +### Fasi Dettagliate: Ingresso e Uscita da un Blocco/Procedura |
|
| 86 | + |
|
| 87 | +Il ciclo di vita di un RdA si divide in fasi ben precise gestite dal chiamante e dal chiamato. |
|
| 88 | + |
|
| 89 | +**A. Ingresso nel blocco (Sequenza di chiamata e Prologo):** |
|
| 90 | + |
|
| 91 | +1. Modifica del Contatore di Programma (salvataggio dell'indirizzo di ritorno). |
|
| 92 | +2. Allocazione del nuovo RdA sulla pila (si aggiorna il Puntatore al Top, in quanto la pila cresce). |
|
| 93 | +3. Aggiornamento del Puntatore all'RdA (che ora punterà al nuovo blocco). |
|
| 94 | +4. Passaggio dei parametri. |
|
| 95 | +5. Salvataggio dello stato dei registri della CPU (per poterli ripristinare dopo). |
|
| 96 | +6. Eventuali inizializzazioni. |
|
| 97 | +7. Trasferimento effettivo del controllo al codice della procedura. |
|
| 98 | + |
|
| 99 | +**B. Uscita dal blocco (Sequenza di uscita ed Epilogo):** |
|
| 100 | + |
|
| 101 | +1. Restituzione dei valori/risultati dal chiamato al chiamante. |
|
| 102 | +2. Ripristino dei registri della CPU salvati in precedenza. |
|
| 103 | +3. **Ripristino del vecchio Puntatore all'RdA** (si segue la catena dinamica per tornare all'RdA precedente). |
|
| 104 | +4. Eventuale finalizzazione. |
|
| 105 | +5. Deallocazione dello spazio sulla pila (si arretra il Puntatore al Top). |
|
| 106 | +6. Ripristino del Contatore di Programma all'indirizzo di ritorno salvato. |
|
| 107 | + |
|
| 108 | +### 4. Esempio di Ricorsione: Il Fattoriale |
|
| 109 | + |
|
| 110 | +Prendiamo come esempio la funzione fattoriale: `fact(n) { if (n<=1) return 1; else return n * fact(n-1); }` |
|
| 111 | + |
|
| 112 | + |
|
| 113 | +- Quando chiamiamo `fact(3)`, viene creato un RdA. |
|
| 114 | +- Poiché la funzione deve calcolare `3 * fact(2)`, la moltiplicazione rimane "in sospeso". Viene salvato lo stato e generato un _nuovo_ RdA per `fact(2)`, impilato sopra il precedente. |
|
| 115 | +- Questo processo si ripete fino al caso base `fact(1)`. |
|
| 116 | +- _Nota sulla memoria:_ I nomi delle variabili ("n", "fact(n-1)") non esistono fisicamente in memoria a run-time, ma usiamo gli offset. La complessità spaziale di questa ricorsione standard è **lineare $O(n)$**, poiché avremo $n$ Record di Attivazione contemporaneamente sulla pila prima di iniziare le moltiplicazioni a ritroso. (Altre funzioni come Fibonacci standard hanno complessità spaziale e temporale peggiore). |
|
| 117 | + |
|
| 118 | +> C'è un caso speciale molto importante in cui possiamo risparmiare tantissima memoria. |
|
| 119 | +> |
|
| 120 | +> - Se la chiamata ricorsiva è **l'ultimissima operazione** eseguita dalla funzione prima di ritornare (es. `return fact_tail(n-1, accumulatore)` senza moltiplicare nulla dopo), si parla di **ricorsione in coda**. |
|
| 121 | +> - In questo caso, non c'è alcun calcolo lasciato "in sospeso" nell'RdA corrente. |
|
| 122 | +> - **L'ottimizzazione:** Invece di allocare un nuovo RdA per la chiamata successiva, il compilatore può **riutilizzare lo stesso identico Record di Attivazione**, sovrascrivendo i vecchi parametri con i nuovi. Questo abbatte la complessità spaziale da lineare a costante $O(1)$. |
|
| 123 | +> - Spesso, per trasformare una ricorsione normale in una ricorsione in coda, il programmatore deve aggiungere un parametro aggiuntivo (un "accumulatore") per portarsi dietro il risultato parziale durante le chiamate. |
|
| 124 | + |
|
| 125 | +### Allocazione Dinamica con Heap |
|
| 126 | + |
|
| 127 | +L'**Heap** è una regione di memoria in cui i blocchi possono essere allocati e deallocati in momenti arbitrari. È fondamentale per la gestione di dati dinamici. |
|
| 128 | + |
|
| 129 | +- **Quando è necessario:** |
|
| 130 | + - Quando il linguaggio permette l'allocazione esplicita a run-time (es. tramite puntatori per strutture dati dinamiche come alberi e liste). |
|
| 131 | + - Per oggetti di dimensione variabile (es. stringhe, insiemi). |
|
| 132 | + - Per oggetti la cui "vita" non segue una logica LIFO (Last In, First Out) come avviene invece per la Pila (Stack). |
|
| 133 | +- **Sfide principali:** La gestione non è banale; richiede di bilanciare la velocità di accesso con l'efficienza dello spazio (per evitare la frammentazione). |
|
| 134 | + |
|
| 135 | +### Gestione dei Blocchi e Frammentazione |
|
| 136 | + |
|
| 137 | + |
|
| 138 | +L'Heap può essere gestito con blocchi di dimensione fissa o variabile, e utilizza una **Lista Libera (LL)** per tenere traccia dello spazio disponibile. |
|
| 139 | + |
|
| 140 | +- **Blocchi a dimensione fissa:** L'Heap è diviso in blocchi di ugual misura. L'allocazione fornisce uno o più blocchi contigui; la deallocazione li restituisce alla LL. |
|
| 141 | +- **Blocchi a dimensione variabile:** All'inizio l'Heap è un unico grande blocco. Durante l'allocazione si cerca un blocco libero della dimensione adatta. |
|
| 142 | + |
|
| 143 | +**Il problema della Frammentazione:** |
|
| 144 | + |
|
| 145 | +- **Frammentazione Interna:** Si verifica quando lo spazio richiesto ($x$) è minore della dimensione del blocco allocato ($y$). Poiché $y > x$, lo spazio in eccesso all'interno del blocco viene sprecato. |
|
| 146 | +- **Frammentazione Esterna:** C'è abbastanza memoria libera totale per soddisfare una richiesta, ma è inutilizzabile perché divisa in "frammenti" troppo piccoli e non contigui. (Ricorda: la memoria allocata deve essere contigua, ad esempio per accedere a un array tramite offset). |
|
| 147 | +- _Soluzione teorica ma inefficace:_ Spostare tutti gli indirizzi e ricompattare la memoria. È un'operazione computazionalmente troppo costosa. |
|
| 148 | + |
|
| 149 | +### Gestione della Lista Libera (LL) |
|
| 150 | + |
|
| 151 | + |
|
| 152 | +Per combattere la frammentazione e ottimizzare le ricerche, si usano diverse strategie per gestire la Lista Libera. |
|
| 153 | + |
|
| 154 | +**A. Unica Lista Libera:** |
|
| 155 | +All'inizio contiene un solo blocco grande quanto tutto l'Heap. Ad ogni richiesta, si cerca un blocco grande abbastanza. |
|
| 156 | + |
|
| 157 | +- **Metodi di ricerca:** |
|
| 158 | + - **First Fit:** Sceglie il _primo_ blocco grande abbastanza. (Vantaggio: molto veloce). |
|
| 159 | + - **Best Fit:** Sceglie il blocco di dimensione _più piccola_ tra quelli grandi abbastanza. (Vantaggio: ottimizza lo spazio). |
|
| 160 | + |
|
| 161 | +> Nota: Se il blocco scelto è troppo grande, viene diviso in due: la parte usata viene allocata, quella in eccesso resta nella LL. Quando un blocco viene deallocato, se confina con un altro blocco libero, i due vengono "fusi" in un unico blocco più grande. |
|
| 162 | + |
|
| 163 | +**B. Liste Libere Multiple:** |
|
| 164 | +Si usano liste separate per blocchi di dimensioni diverse. |
|
| 165 | + |
|
| 166 | +- **Buddy System (Sistema dei Gemelli):** Si usano $k$ liste. La lista $k$ contiene blocchi di dimensione $2^k$. Se serve un blocco $2^k$ e non c'è, si prende un blocco dalla lista $2^{k+1}$ e lo si divide in due metà (i "buddy"). Quando un blocco viene deallocato, se anche il suo buddy è libero, si fondono tornando un blocco di dimensione $2^{k+1}$. |
|
| 167 | +- **Fibonacci System:** Simile al Buddy System, ma le dimensioni seguono la sequenza di Fibonacci anziché le potenze di 2 (crescono più lentamente, riducendo lo spreco interno). |
|
| 168 | + |
|
| 169 | +--- |
|
| 170 | + |
|
| 171 | +### Implementazione delle Regole di Scope (Visibilità) |
|
| 172 | + |
|
| 173 | +Come fa il programma a sapere a quale variabile ci stiamo riferendo se ci sono più variabili con lo stesso nome? |
|
| 174 | + |
|
| 175 | +- **Scope Statico (Risolto a compile-time):** |
|
| 176 | + - Si implementa tramite **Catena Statica** (puntatori che risalgono ai blocchi annidati nel codice sorgente) o tramite **Display** (un array di puntatori per velocizzare l'accesso). |
|
| 177 | +- **Scope Dinamico (Risolto a run-time):** |
|
| 178 | + - Si implementa tramite **A-list** (Association List) o **Tabella Centrale dell'Ambiente (CRT)**. |
|
| 179 | + |
|
| 180 | +### Esempio Pratico: Legame Corretto (Scope Statico) |
|
| 181 | + |
|
| 182 | +Consideriamo il seguente pseudo-codice: |
|
| 183 | + |
|
| 184 | +```c |
|
| 185 | +{ int x = 10; // x globale |
|
| 186 | + void foo() { x++; } // foo incrementa la x globale |
|
| 187 | + void fie() { |
|
| 188 | + int x = 0; // x locale a fie |
|
| 189 | + foo(); |
|
| 190 | + } |
|
| 191 | + // Esecuzione (Main) |
|
| 192 | + fie(); |
|
| 193 | + foo(); |
|
| 194 | +} |
|
| 195 | +``` |
|
| 196 | + |
|
| 197 | +**Come si comporta in memoria?** |
|
| 198 | +Il codice della funzione `foo` deve accedere sempre alla stessa variabile `x` (quella dichiarata globalmente a valore 10, memorizzata nel Record di Attivazione - RdA - del main). |
|
| 199 | + |
|
| 200 | +- Anche se chiamiamo `foo` dall'interno di `fie` (dove esiste una x locale a 0), lo scope statico impone che `foo` "veda" solo l'ambiente in cui è stata definita (il main). |
|
| 201 | +- Il meccanismo: A run-time, in cima alla pila c'è l'RdA di `foo` (il Puntatore SP guarda lì). Per trovare la x giusta, il sistema non la cerca semplicemente "indietro" nella pila dinamica, ma usa i puntatori di scope per determinare prima qual è l'RdA corretto a cui appartiene quella variabile (in questo caso l'RdA del main). Una volta trovato l'RdA giusto, accede a x tramite l'offset calcolato rispetto a *quell'*RdA, ignorando totalmente la x locale di fie. |
|
| 202 | + |
|
| 203 | + |
|
| 204 | +x tramite offset relativo a tale RdA (e non relativo a SP) |
|
| 205 | + |
|
| 206 | +Record di attivazione per scoping statico |
|
| 207 | + |
|
| 208 | +Link dinamico: |
|
| 209 | +– puntatore all’RdA precedente sulla pila (RdA del chiamante) |
|
| 210 | +• Link statico: |
|
| 211 | +– puntatore all’RdA del blocco che contiene immediatamente il testo del blocco in esecuzione |
|
| 212 | +• Osserva: |
|
| 213 | +– link dinamico dipende dalla sequenza di esecuzione del programma |
|
| 214 | +– link statico dipende dall’annidamento statico (nel testo) delle dichiarazioni delle procedure |
|
| 215 | + |
|
| 216 | +Catena Statica: esempio |
|
| 217 | +Sequenza di chiamate a run time A, B, C, D, E, C |
|
| 218 | + |
|
| 219 | +le linee tratteggiate sono link statici |
|
| 220 | + |
|
| 221 | + |
|
| 222 | +Se un sottoprogramma è annidato a livello k, allora la catena è lunga k |
|
| 223 | + |
|
| 224 | +se sono in e e sto cercando una var x non locale allora vado in c e poi vado in a |
|
| 225 | +questo grazie ai link statici (questi puntatori sono determinati a runtime) |
|
| 226 | +Esempio |
|
| 227 | + |
|
| 228 | +``` |
|
| 229 | +{int x; |
|
| 230 | +void A(){ |
|
| 231 | + x=x+1;} |
|
| 232 | +void B(){ |
|
| 233 | + int x; |
|
| 234 | + void C (int y){ |
|
| 235 | + int x; |
|
| 236 | + x=y+2; A(); |
|
| 237 | + } |
|
| 238 | + x=0; A(); C(3); |
|
| 239 | +} |
|
| 240 | +x=10; |
|
| 241 | +B(); |
|
| 242 | +} |
|
| 243 | +``` |
|
| 244 | + |
|
| 245 | +Struttura main con dentro a e b e b con dentro c |
|
| 246 | + |
|
| 247 | + |
|
| 248 | +la x che viene modificata e' sempre quella del main visto che viene modificata da A e il puntatore di catena statica di A punta al main. Le altre x non vengono modificate da A. |
|
| 249 | +C modifica la propria x (visto che la dichiara ed e'quindi locale) |
|
| 250 | + |
|
| 251 | +il compilatore dice di risalire di 1 il record di attivazione. A lo fa e trova a x del main (con un offset) |
|
| 252 | + |
|
| 253 | +se in C avessi la variabile pippo chee e' definita nel main il compilatore mi direbbe che pippo e' definito in main (2 livelli sopra) e quindi quando devo manipolarla o chiamarla salgo due livelli e ne uso il valore |
|
| 254 | + |
|
| 255 | +Il compilatore sa dove sono dichiarate le variabili ma non sa la loro posizione a runtime (quindi ci dice solo quanto andare in su ma il dove va deciso a runtime grazie alla catena statica) |
|
| 256 | + |
|
| 257 | +Dal punto di vista del supporto a run time |
|
| 258 | +Come viene determinato il link statico del chiamato? |
|
| 259 | + |
|
| 260 | +es sopra |
|
| 261 | +sono nel main, chiamo B devo inizializzre il puntatore di catena statica di B, so che B e' inizializzato dentro al main quindi inizializzo il suo puntatore al main (me) |
|
| 262 | +Ora B chiama A, come posso inizializzare il suo puntatore? B sa che A e' allo stesso livello di annidamento di A e quindi gli basta risalire di (0) livelli e passare il puntatore a A. |
|
| 263 | +In generale o X e' inizializzato dentro a A (e quindi il suo indirizzo e' A) oppure Si calcola livello di annidamento di A - X e si risalgono i A-X livelli e si assegna l'indirizzo a X |
|
| 264 | + |
|
| 265 | +e'il chiamante a determinare il link statico del chiamato |
|
| 266 | +Info a disposizione del chiamante: |
|
| 267 | + |
|
| 268 | +- annidamento statico dei blocchi (determinata dal compilatore) |
|
| 269 | +- proprio RdA |
|
| 270 | + |
|
| 271 | +Come determinare il puntatore di Catena statica (CS) |
|
| 272 | +il chiamante Ch conosce l'annidamento dei blocchi: |
|
| 273 | + |
|
| 274 | +- quando Ch chiama P sa se la definizione di P e': |
|
| 275 | + - immediatamente inclusain Ch (k=0) |
|
| 276 | + - in un blocco k passi fuori da Ch |
|
| 277 | + |
|
| 278 | +– nessun altro caso possibile: |
|
| 279 | +• perché P deve essere in scope! |
|
| 280 | +– nel caso a destra: |
|
| 281 | +• chiamate: A, B, C, D, E, C |
|
| 282 | +– con i dati di catena statica: |
|
| 283 | +• A; (B,0); (C,1); (D,0); (E,1); (C,2) |
|
| 284 | + |
|
| 285 | +Se k=0: |
|
| 286 | +– Ch passa a P il proprio SP |
|
| 287 | +•Se k>0: |
|
| 288 | +– Ch risale la propria catena statica di k passi e passa il puntatore così determinato |
|
| 289 | + |
|
| 290 | + |
|
| 291 | +> Nota: Se B chiamasse D non potrebbe farlo perche' non lo puo' vedere. |
|
| 292 | + |
|
| 293 | +Ripartizione dei compiti |
|
| 294 | +Compilatore: |
|
| 295 | + |
|
| 296 | +- associa l'informazione k ad ogni chiamata |
|
| 297 | +- associa ad ogni nome un indice h: |
|
| 298 | + - h=0: nome locale |
|
| 299 | + - h diverso da 0: nome non locale definito h blocchi sopra |
|
| 300 | +- sequenza chiamata/prologo |
|
| 301 | + - risale la catena statica |
|
| 302 | + - inizializza il puntatore di catena statica |
|
| 303 | +- Costi: |
|
| 304 | + - per ogni chiamata: k passi di catena statica |
|
| 305 | + - ad ogni accesso ad una variabile non locale:(h passi di catena statica in piu rispetto all'accesso ad un locale) |