Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
Nel momento in cui, durante l'esecuzione di un programma, si entra in un nuovo blocco vengono
create le associazioni fra i nomi dichiarati localmente al blocco e disattivate le associazioni per quei
nomi già esistenti all'esterno del blocco che sia ridefiniti al suo interno. Nel caso dell'uscita dal
nuovo blocco, invece, si verifica l'opposto le associazioni precedentemente create vengono distrutte
e quelle disattivate vengono riattivate. Possiamo identificare delle operazioni sui nome e
sull'ambiente, quali riferimento di oggetto denotato mediante il suo nome, creazione, disattivazione,
riattivazione e distruzione e di un'associazione fra il nome e l'oggetto denotato. Per quanto riguarda
gli oggetti denotabili sono possibili le operazioni di creazione, accesso, modifica e distruzione.
In molti casi le operazioni di creazione fra nome e oggetto avvengono allo stesso tempo, ma non
vale sempre. In generale non è detto che il tempo di vita di un oggetto denotabile, ossia il tempo che
intercorre fra la sua creazione e la sua distruzione, coincida con il tempo di vita dell'associazione fra
nome e oggetto. Infatti un oggetto può avere un tempo di vita maggiore di quello della sua
associazione con un nome. Può accadere anche che il tempo di vita di un 'associazione sia superiore
a quello dell'oggetto, cioè può avvenire che un nome permetta di accedere ad un oggetto che non
esiste più. Questo può accadere, per esempio, se si passa per riferimento un oggetto creato e quindi
si dealloca la memoria per tale oggetto prima che la procedura termini.
Regole di scope
In un linguaggio con scope statico, un qualsiasi ambiente esistente dipende solo dalla strutture
sintattica del programma. L a regola dello scope statico è definita dai seguenti tre punti:
1. Le dichiarazioni locali di un blocco definiscono l'ambiente locale di quel blocco e non
quelle presenti nei suoi eventuali blocchi annidati.
2. Se si usa un nome all'interno di un blocco, l'associazione valida per tale nome è quella
presente nell'ambiente locale del blocco, se esiste. Se non esiste, si considerano le
associazione esistenti nell'ambiente locale del blocco immediatamente esterno che contiene
il blocco.
3. Un blocco può avere un nome, nel qual caso tale nome fa parte dell'ambiente locale del
blocco immediatamente esterno che contiene il blocco a cui abbiamo dato un nome.
Lo scope statico ha due importanti conseguenze positive; innanzitutto in programmatore ha una
migliore comprensione del programma osservandone la struttura testuale, inoltre il compilatore può
collegare ogni occorrenza di un nome alla sua dichiarazione corretta, questo fa si che siano possibili
a tempo di compilazione un maggior numero di controlli di correttezza. D'altro canto risulta meno
efficiente in fase di esecuzione.
Lo scope dinamico è stato introdotto principalmente per semplificare la gestione a run-time
dell'ambiente. Molti linguaggi lo usano e determinano le associazioni fra nomi e oggetti denotati
seguendo a ritroso l'esecuzione del programma. Tali linguaggi per risolvere i nomi non locali usano
la sola struttura a pila impiegata per la gestione a run-time dei blocchi. Secondo la regola dello
scope dinamico, l'associazione valida per un nome X, in un qualsiasi punto P di un programma, è la
più recente, in senso temporale, associazione creata per X che sia ancora attiva quando il flusso di
esecuzione arriva a P. Questo metodo ha il vantaggio di avere un'ottima flessibilità, ma ha una
minore leggibilità e una complicata gestione a run-time.
Esistono alcuni problemi di scope; le differenza maggiori fra le regole di scope statico sono relative
a dove possono essere introdotte le dichiarazioni e a quale sia l'esatta visibilità delle variabili locali.
Nel caso del Pascal le dichiarazioni possono comparire solo all'inizio del blocco e prima di essere
usati, inoltro lo scope di un nome si estende dall'inizio alla fine del blocco nel quale appare la
dichiarazione. Nel caso del C e di ADA lo scope della dichiarazione viene limitato alla porzione di
blocco compresa fra il punto in cui la dichiarazione compare e la fine del blocco stesso; anche in
questi linguaggi i nomi devono essere dichiarati prima di essere usati. Però la dichiarazione prima
dell'uso in alcuni casi è particolarmente gravosa: impedisce infatti la definizione di tipi ricorsivi. In
JAVA è consentito che una dichiarazione possa apparire in un punto qualsiasi di un blocco. Se la
dichiarazione corrisponde ad una variabile, lo scope del nome dichiarato si estende dal punto della
dichiarazione fino alla fine del blocco; se la dichiarazione invece si riferisce al membro di una
classe, essa è visibile in tutta la classe nella quale compare.
5 – Gestione della Memoria
Tecniche di gestione della memoria
La gestione della memoria costituisce una delle funzionalità dell'interprete, quali la gestione
dell'allocazione di memoria per i programmi e i dati: come disporli in memoria, quanto tempo ci
devono rimanere e quali strutture ausiliarie siano necessarie. Nel caso di una macchina stratta di
baso livello tipo la macchina hardware, la gestione della memoria è molto semplice e può essere
interamente statica. Nel caso di un linguaggio di alto livello l'allocazione statica della memoria non
è più sufficiente, per cui dobbiamo usare una gestione dinamica della memoria. Questa gestione può
essere realizzata con una pila, ma ci sono casi dove la pila non è sufficiente; in questo caso si usa
una struttura detta heap.
Gestione statica della memoria
La memoria gestita staticamente è quella allocata dal compilatore prima dell'esecuzione. Gli oggetti
per i quali la memoria è allocata staticamente risiedono in una zona fissa di memoria per tutta la
durata dell'esecuzione. Tipici elementi per i quali è possibile allocare staticamente la memoria sono
le variabili locali, le istruzioni del codice oggetto prodotte dal compilatore, le costanti e le varie
tabelle prodotte dal compilatore necessarie per il supporto a run-time del linguaggio. Nel caso in cui
il linguaggio non sopporti la ricorsione, è possibile gestire staticamente anche a memoria per le
rimanenti componenti del linguaggio: sostanzialmente si tratta di associare, staticamente, ad ogni
procedura una zona di memoria nella quale memorizzare le informazioni della procedura stessa.
Gestione dinamica mediante pila
La maggior parte dei linguaggi di programmazione moderni permette una strutturazione a blocchi
dei programmi. I blocchi vengono aperti e chiusi usando la politica LIFO: quando si entra in un
blocco A e poi in un blocco B, prima di uscire da A si deve uscire da B. Lo spazio di memoria
allocato sulla pila è detto record di attivazione (RdA). Il record di attivazione è associato ad una
specifica attivazione di procedura e non ad una sua dichiarazione. La pila sulla quale sono
memorizzati i record di attivazione è detta pila a run-time.
La struttura di un record di attivazione per un blocco in-line è composta da vari settori che
contengono le seguenti informazioni:
Risultati intermedi: nel caso in cui si debbano effettuare dei calcoli può essere necessario
– memorizzare alcuni risultati intermedi.
Variabili locali: sono dichiarate all'interno di un blocco, devono avere a disposizione uno
– spazio di memoria la cui dimensione dipenderà dal numero e dal tipo delle variabili; in
alcuno casi vi possono essere dichiarazioni che dipendono da valori noti solo al momento
dell'esecuzione, come ad esempio gli array dinamici. In questi casi il record di attivazione
prevede anche una parte di dimensione variabile che sarà definita al momento
dell'esecuzione.
Puntatore di catena dinamica: questo campo serve per memorizzare il puntatore al
– precedente record di attivazione sulla pila, essendo gli Rda con dimensioni diverse; l'insieme
dei collegamenti realizzati da questi puntatori è detto catena dinamica.
La struttura di un record di attivazione per le procedure e le funzioni è formato dai seguenti campi:
Risultati intermedi, variabili locali e puntatore di catena dinamica.
– Puntatore di catena statica: serve per gestire le informazioni necessarie a realizzare le regole
– di scope statico.
Indirizzo di ritorno: contiene l'indirizzo della prima istruzione da eseguire dopo che la
– chiamata alla procedura ha terminato l'esecuzione.
Indirizzo del risultato: serve solo per le funzioni e restituisce l'indirizzo della memoria dove
– viene depositato il risultato.
Parametri: contiene i valori dei parametri attuali usati nella chiamata della procedura.
–
Per gestire la pila viene usato un puntatore esterno alla pila che indica l'ultimo RdA inserivo nella
pila stessa, chiamato puntatore ad record di attivazione. Un altro puntatore, chiamato puntatore alla
pila, indica la prima posizione di memoria libera nella pila. I record di attivazione vengono inseriti e
rimossi dalla pila a tempo di esecuzione: quando si entra in un blocco, o si chiama una procedura, il
relativo RdA verrà inserito nella pila, per poi essere eliminato quando si esce dal blocco o quando
termina l'esecuzione della procedura. La gestione a run-time della pila di sistema è realizzata da
alcuni frammenti di codice che il compilatore inserisce prima e dopo la chiamata di una procedura o
prima dell'inizio e dopo la fine di un blocco. La gestione della pila è fatta sia dal programma o
procedura chiamante che dal programma o procedura chiamato. Per questo scopo nel chiamante
viene aggiunta una parte di codice detta sequenza di chiamata che è eseguita in parte
immediatamente prima della chiamata di procedura, e in parte immediatamente dopo la
terminazione della procedura. Nel chiamato invece viene aggiunto un prologo, da eseguirsi subito
dopo la chiamata, ed un epilogo, da eseguirsi al termine dell'esecuzione della procedura. Al
momento della chiamata di procedura la sequenza di chiamata ed il prologo si devo occupare delle
seguenti attività:
Modifica del valore del contatore programma
– Allocazione dello spazio sulla pila
– Modifica del puntatore RdA
– Passaggio dei parametri
– Salvataggio dei registri
– Esecuzione del codice per l'inizializzazione
–
Al momento del ritorno del controllo al programma chiamante, quando la procedura chiamata
termina la sua esecuzione, l'epilogo e la sequenza di chiamata devono invece gestire le seguenti
operazioni:
Ripristino del valore del contatore programma
– Restituzione dei valori
– Ripristino dei registri
– Esecuzione del codice per la finalizzazione
– Deallocazione dello spazio sulla pila
–
Gestione dinamica mediante heap
Nel caso in cu