memoria gestita staticamente allocata dal compilatore prima dellesecuzione

  • Slides: 47
Download presentation
memoria gestita staticamente: allocata dal compilatore prima dell’esecuzione in una zona fissa della memoria

memoria gestita staticamente: allocata dal compilatore prima dell’esecuzione in una zona fissa della memoria (stabilita dal compilatore) – variabili globali – istruzioni del codice oggetto – costanti – tabelle del compilatore per il supporto a run-time del linguaggio (gestione nomi, type checking, garbage collection) – informazioni locali alle procedure (se non è supportata la ricorsione): variabili locali, parametri indirizzo di ritorno

memoria gestita dinamicamente (pila a run-time); blocchi in-line A: { int a = 1;

memoria gestita dinamicamente (pila a run-time); blocchi in-line A: { int a = 1; int b = 0; B: { int c = 2; int b = 3; } b = a+1; } push a; push b push c; push b pop

record di attivazione (frame): lo spazio di memoria allocato sulla pila e dedicato a

record di attivazione (frame): lo spazio di memoria allocato sulla pila e dedicato a un blocco in-line (o a una procedura attivata); per i blocchi in-line contiene: – risultati intermedi (risultati senza nome esplicito) – variabili locali (interne al blocco, dimensione nota) – puntatore di catena dinamica (link dinamico o di controllo; punta al precedente Rd. A sulla pila)

per le procedure contiene: – risultati intermedi (risultati senza nome esplicito) – variabili locali

per le procedure contiene: – risultati intermedi (risultati senza nome esplicito) – variabili locali (interne al blocco e di dimensione nota non array dinamici, per esempio) – puntatore di catena dinamica (link dinamico o di controllo; punta al precedente Rd. A sulla pila) – puntatore di catena statica (informazioni per gestire lo scope statico) – indirizzo di ritorno, indirizzo del risultato; – parametri; i nomi sono memorizzati nel Rd. A ? offset rispetto all’indirizzo del blocco in cui la variabile è memorizzata

gestione a run-time della pila di sistema il compilatore inserisce frammenti di codice prima

gestione a run-time della pila di sistema il compilatore inserisce frammenti di codice prima o dopo la chiamata di procedura (o di un blocco) – sequenza di chiamata: codice aggiunto al chiamante ed eseguito prima della chiamata di procedura e dopo la terminazione della procedura chiamata – prologo: codice aggiunto al chiamato ed eseguito subito dopo la chiamata – epilogo: codice aggiunto al chiamato ed eseguito al termine della procedura chiamata come sono distribuite queste attività ?

sequenza di chiamata (chiamante) e prologo (chiamato) – modifica del program counter (e salvataggio

sequenza di chiamata (chiamante) e prologo (chiamato) – modifica del program counter (e salvataggio del vecchio valore per mantenere l’indirizzo di ritorno) – allocazione spazio sulla pila (e modifica del puntatore alla memoria libera sulla pila) – modifica del puntatore al (nuovo) Rd. A – passaggio dei parametri (fatto dal chiamante) – salvataggio dei registri (fra cui il vecchio puntatore al Rd. A) – esecuzione codice di inizializzazione

epilogo (chiamato) e sequenza di chiamata (chiamante) – ripristino del program counter – restituzione

epilogo (chiamato) e sequenza di chiamata (chiamante) – ripristino del program counter – restituzione dei valori (parametri o valore funzione) – ripristino registri (fra cui il vecchio puntatore al Rd. A) – esecuzione codice di finaizzazione – deallocazione spazio sulla pila (il Rd. A della procedura terminata è rimosso dalla pila) di quali strutture dati ho bisogno per implementare questa gestione? cosa succede se posso allocare e deallocare esplicitamente la memoria?

memoria gestita dinamicamente (heap) int *p, *q; p = malloc(sizeof(int)); q = malloc(sizeof(int)); *p

memoria gestita dinamicamente (heap) int *p, *q; p = malloc(sizeof(int)); q = malloc(sizeof(int)); *p = 0; *q = 1; free(p); free(q): le operazioni di deallocazione della memoria sono fatte nello stesso ordine di quelle di allocazione; no gestione LIFO heap: zona di memoria usata per gestire allocazioni esplicite di memoria (con blocchi a dimensione fissa o variabile)

heap con blocchi di dimensione fissa (lista libera)

heap con blocchi di dimensione fissa (lista libera)

heap con blocchi di dimensione variabile se il linguaggio permette l’allocazione a run-time di

heap con blocchi di dimensione variabile se il linguaggio permette l’allocazione a run-time di memoria di dimensione variabile, la LL non è adeguata due esigenze contrastanti: – migliorare l’occupazione della memoria (frammentazione interna ed esterna) – velocizzare la gestione dello heap tecniche di allocazione: – unica lista libera – liste libere multiple

unica lista libera - un unico blocco di memoria contiene l’intero heap; se è

unica lista libera - un unico blocco di memoria contiene l’intero heap; se è richiesta l’allocazione di n parole di memoria, il puntatore avanza di n (analogo per le richieste successive) - i blocchi deallocati sono collegati in una lista libera; quando lo heap finisce si passa allo spazio deallocato, in due modi: utilizzando direttamente la lista libera: si cerca nella lista un blocco con dimensione adeguata, seguendo due principi: first fit (miglior tempo di gestione) best fit (migliore occupazione di memoria) compattando la memoria libera: tutti i blocchi attivi spostati all’estremità dello heap (si può fare sempre? )

liste libere multiple - più liste libere, ognuna delle quali con blocchi di dimensioni

liste libere multiple - più liste libere, ognuna delle quali con blocchi di dimensioni diverse se è richiesta l’allocazione di n parole di memoria, è selezionata la lista che contiene blocchi di dimensione appropriata - le dimensioni dei blocchi possono essere statiche o dinamiche buddy system le dimensioni dei blocchi sono potenze di 2; richiedo un blocco pari a n; se k è tale che 2 k n, allora si cerca un blocco libero in lista 2 k; altrimenti, si prende un blocco in 2 k+1, si divide in due, uno è occupato e l’altro entra nella lista 2 k; quando un blocco è liberato, si cerca il suo compagno e si riunificano heap di Fibonacci come sopra, ma con numeri di Fibonacci (che crescono di meno)

implementazione regole di scope (come risolvere un riferimento non locale) scope statico o scope

implementazione regole di scope (come risolvere un riferimento non locale) scope statico o scope annidato più vicino l’ambiente in qualsiasi punto e in qualsiasi momento dipende SOLO dalla struttura sintattica del programma (il Rd. A collegato dal puntatore di catena dinamica NON è il record in cui cercare per risolvere un riferimento non locale) scope dinamico l’associazione valida per un nome X, in un punto P di un programma, è la più recente associazione creata (in senso temporale) per X che sia ancora attiva quando il flusso di esecuzione arriva a P

scope statico: la catena statica A: { int y = 0; B: { int

scope statico: la catena statica A: { int y = 0; B: { int x = 0; void pippo(int n) { x = n+1; y = n+2; } C: { int x = 1; pippo(2); write(x); } } write(x); } scope statico: x (non locale in pippo) è quella dichiarata nel blocco B, non in C

scope statico: la catena statica

scope statico: la catena statica

scope statico: la catena statica

scope statico: la catena statica

con la sequenza di chiamate A B C D E C, si ha

con la sequenza di chiamate A B C D E C, si ha

la gestione della catena statica è fatta nella sequenza di chiamata, nel prologo e

la gestione della catena statica è fatta nella sequenza di chiamata, nel prologo e nell’epilogo; il chiamante calcola il puntatore di catena statica del chiamato e lo passa al chiamato; due casi: – chiamato all’interno del chiamante: il chiamante passa al chiamato il puntatore al proprio Rd. A – chiamato all’esterno del chiamante: il chiamante calcola il puntatore di catena statica deferenziando k+1 volte il proprio puntatore di catena statica, con k = distanza di livello di annidamento fra chiamato e chiamante (nota a compile-time) il chiamato riceve il puntatore di catena statica e lo memorizza nel proprio Rd. A

la tabella dei simboli contiene - i livelli di annidamento di blocchi o procedure

la tabella dei simboli contiene - i livelli di annidamento di blocchi o procedure - in generale, i nomi usati nel programma (il tipo e il numero che indica lo scope che contiene la dichiarazione per tale nome) quindi, si può calcolare a compile-time la distanza fra lo scope della chiamata e lo scope della dichiarazione

come risolvere riferimenti non locali senza fare ricerche sulla pila? basta risalire la catena

come risolvere riferimenti non locali senza fare ricerche sulla pila? basta risalire la catena statica di un numero di link pari alla distanza dichiarazione-chiamata si può risolvere un riferimento non locale completamente a compile-time? no, non sappiamo staticamente il numero di Rd. A sulla pila

scope statico: la catena statica A: { int y = 0; B: { int

scope statico: la catena statica A: { int y = 0; B: { int x = 0; void pippo(int n) { x = n+1; y = n+2; } C: { int x = 1; pippo(2); write(x); } } write(x); } il compilatore sa che per usare y deve risalire di due blocchi; basta memorizzare a compile-time questo numero e usarlo a run-time.

scope statico: il display nome non locale, dichiarato in un blocco esterno di k

scope statico: il display nome non locale, dichiarato in un blocco esterno di k livelli Þ k accessi alla memoria (a run-time) per scorrere la catena statica e trovare il Rd. A in cui è dichiarato display (riduce a 2 il numero di accessi): – vettore di tanti elementi quanti sono i livelli di annidamento dei blocchi – l’elemento k-esimo del display contiene il puntatore al Rd. A di livello di annidamento k attualmente attivo – se cerco un oggetto non locale dichiarato in un blocco di livello k, accedo alla posizione k del vettore e seguo il puntatore

entrando in (o uscendo da) una procedura di livello k, occorre: – aggiornare il

entrando in (o uscendo da) una procedura di livello k, occorre: – aggiornare il display in posizione k con il valore del puntatore al Rd. A del chiamato – salvare il vecchio valore (nel Rd. A del chiamato) due casi: – chiamato all’interno del chiamante: se il chiamante è a livello n, entrambi condividono il display, a cui si aggiunge un nuovo valore in posizione n+1; – chiamato esterno al chiamante: se il chiamante è a livello n e il chiamato a livello m (m<n), essi condividono il display fino alla posizione m-1; l’elemento m è aggiornato con il puntatore al Rd. A del chiamato, ma il vecchio valore deve essere salvato, e servirà quando il chiamato termina;

scope dinamico per risolvere un riferimento a un nome x si risale la pila

scope dinamico per risolvere un riferimento a un nome x si risale la pila fino a trovare il blocco che contiene x; le associazioni nome-oggetto sono memorizzate nel Rd. A

scope dinamico: lista di associazioni le associazioni nome-oggetto possono essere memorizzate in una A(ssociation)-list,

scope dinamico: lista di associazioni le associazioni nome-oggetto possono essere memorizzate in una A(ssociation)-list, gestita come una pila

due inconvenienti: – occorre memorizzare i nomi in strutture presenti a tempo di esecuzione

due inconvenienti: – occorre memorizzare i nomi in strutture presenti a tempo di esecuzione (al contrario dello scope statico); • • è ovvio per la A-list anche per il caso Rd. A, perche’ uno stesso nome può essere memorizzato in posti diversi in diversi Rd. A – inefficienza della ricerca a run-time

tabella centrale dell’ambiente (limita i due inconvenenti): tutti i blocchi del programma fanno riferimento

tabella centrale dell’ambiente (limita i due inconvenenti): tutti i blocchi del programma fanno riferimento a una sola tabella (CRT), che contiene: – tutti nomi usati nel programma, e per ogni nome – un flag che indica se l’’associazione per il nome è attiva – un puntatore alle informazioni sull’oggetto – i nomi hanno posizioni fisse in tabella (perché tutti gli identificatori usati nel programma sono noti), quindi l’accesso a run-time è costante – se dal blocco A si entra in B, la CRT è modificata per descrivere il nuovo ambiente di B; le associazioni deattivate devono essere salvate (per quando esco da B)

– a ogni nome della CRT è associata una pila che contiene nella prima

– a ogni nome della CRT è associata una pila che contiene nella prima posizione l’associazione valida e (nelle successive) quelle disattivate – oppure si usa una sola pila nascosta per memorizzare tutte le associazioni deattivate per ogni nome

usando la CRT (con pila nascosta o no) per conoscere una associazione dell’ambiente occorre:

usando la CRT (con pila nascosta o no) per conoscere una associazione dell’ambiente occorre: – accedere alla tabella (diretto) – accedere alla memoria con il puntatore trovato nella tabella – non serve nessuna ricerca a run-time