nome: sequenza di caratteri usata per denotare un oggetto nomi ed oggetti denotabili int pippo; int pluto() { pippo=1; } distinguere il nome e l’oggetto denotato uno stesso oggetto può avere più nomi (aliasing) uno stesso nome può denotare oggetti diversi in momenti diversi l’uso dei nomi realizza la più semplice astrazione sui dati (l’ambiente è la parte dell’implementazione responsabile dell’associazione nome-oggetto denotato) l’uso dei nomi realizza la più semplice astrazione sul controllo nome: sequenza di caratteri usata per denotare un oggetto
oggetti denotabili: oggetti a cui posso assegnare un nome oggetti i cui nomi sono definiti dall’utente (variabili, parametri, procedure tipi definiti, costanti simboliche, …) oggetti i cui nomi sono definiti dal linguaggio di programmazione (tipi primitivi, operazioni primitive, …) il binding (legame) nome-oggetto avviene in differenti fasi: progettazione del linguaggio scrittura del programma compile-time run-time
ambiente: insieme delle associazioni nome-oggetto esistenti a run-time in uno specifico punto del programma ed in uno specifico momento dichiarazione: costrutto del linguaggio che permette di introdurre un’associazione nell’ambiente int x; int f() { return 0; } typedef T = int;
stesso nome denota oggetti diversi { int pippo; pippo = 2; { char pippo; pippo = a; } } stesso oggetto denotato da più nomi in ambienti diversi una variabile passata per riferimento è accessibile: con il suo nome nel programma chiamante con il nome del parametro formale nella procedura variabile intera variabile carattere
X e Y puntano alla stessa locazione stesso oggetto denotato da più nomi, stesso ambiente int *X, *Y; X = (int *) malloc (sizeof (int)); *X = 5; Y = X; *Y = 10; write(*X); stesso nome, nello stesso ambiente, denota oggetti diversi a seconda del flusso di esecuzione del programma procedura ricorsiva che dichiara un nome locale scope dinamico aliasing il risultato è 10, perché X e Y puntano alla stessa locazione
blocco: regione testuale di un programma, individuata da un segnale di inizio e uno di fine, che può contenere dichiarazioni locali a quella regione blocco associato a una procedura blocco in-line (o anonimo) { int pippo; pippo = 2; { char pippo; pippo = a; } } blocco in-line apri blocco A; apri blocco B; chiudi blocco A; chiudi blocco B; mai permesso i cambiamenti dell’ambiente avvengono all’entrata e all’uscita dai blocchi (anche annidati)
regola di visibilità canonica: una dichiarazione locale in un blocco è visibile: in quel blocco in tutti i blocchi annidati salvo ri-dichiarazioni dello stesso nome (mascheramento)
ambiente associato a un blocco: ambiente locale (nomi dichiarati localmente al blocco, eventualmente parametri locali) ambiente non locale (nomi dichiarati fuori dal blocco, ma comunque visibili) ambiente globale (nomi creati all’inizio del programma, usabili in tutti i blocchi)
A: { int a =1; B: { int b=2; int c=2; C: { int c=3; int d; d=a+b+c; write(d); } D: { int e; e=a+b+c; write(e); }}} associazione nell’ambiente globale ambiente locale di B + ambiente globale ambiente locale di C + ambiente non locale + ambiente globale d=6; l’ambiente locale di C maschera quello di B nell’ambiente non locale di D non compare d e=5
operazioni sull’ambiente entrando in un blocco: creazione delle associazioni fra i nomi locali al blocco e gli oggetti denotati disattivazione delle associazioni per i nomi ridefiniti uscendo dal blocco: distruzione delle associazioni fra i nomi locali al blocco e gli oggetti denotati riattivazione delle associazioni per i nomi che erano stati ridefiniti
operazioni sui nomi e sull’ambiente naming: creazione di associazione fra nome e oggetto denotato (dichiarazione locale al blocco o parametro) referencing: riferimento di un oggetto denotato mediante il suo nome (uso del nome per accedere all’oggetto denotato) disattivazione di associazione fra nome e oggetto denotato (la nuova associazione per un nome maschera la vecchia associazione, che rimane disattivata fino all’uscita dal blocco) riattivazione di associazione fra nome e oggetto denotato (una vecchia associazione che era mascherata è riattivata all’uscita da un blocco) unnaming: distruzione di associazione fra nome e oggetto denotato (all’uscita di un blocco)
operazioni su oggetti denotabili creazione: allocazione della memoria e inizializzazione accesso e modifica: tramite il nome distruzione: deallocazione della memoria
quale valore di x è stampato? regole di scope A: { int x=0; void pippo(){ x=1;} B: { int x; pippo(); } write(x); quale valore di x è stampato?
regola di visibilità canonica: una dichiarazione locale in un blocco è visibile: in quel blocco in tutti i blocchi annidati salvo ri-dichiarazioni dello stesso nome (mascheramento) MA la regola di visibilità (cioè l’annidamento) è basata: sul testo del programma (scope statico) ? sul flusso di esecuzione (scope dinamico)?
che succede al compilatore con questa regola? scope statico o scope annidato più vicino l’ambiente in qualsiasi punto e in qualsiasi momento dipende SOLO dalla struttura sintattica del programma (basic, algol, ada, pascal, java) cioè le dichiarazioni locali in un blocco includono solo quelle presenti nel blocco, e non quelle dei blocchi annidati; se si usa un nome in un blocco, l’associazione valida è quella locale al blocco; se non esiste, si prende la prima associazione valida a partire dal blocco immediatamente esterno, dal più vicino al più lontano; se non esiste, si considera l’ambiente predefinito del linguaggio; se non esiste, errore; se il blocco ha un nome, allora il nome fa parte dell’ambiente locale del blocco immediatamente esterno (caso procedure). che succede al compilatore con questa regola?
esempio con scope statico { int x=0; void pippo(int n) {x=n+1;} write(x); {int x=0; } stampa 4 stampa 0 stampa 4
lo scope statico permette di determinare tutti gli ambienti di un programma leggendone il testo migliore comprensione del programma controlli di correttezza a compile-time ottimizzazione del codice a compile-time problema del tipaggio statico e della sicurezza dei tipi gestione a run-time complicata (gli ambienti non locali evolvono diversamente dal flusso di attivazione e disattivazione dei blocchi)
void pippo() {write(x);} void pluto() {const x=1; pippo();} pluto(); } con lo scope statico la sequenza di blocchi da considerare per risolvere i riferimenti a nomi non locali è diversa da quella dei blocchi aperti e chiusi durante l’esecuzione (gestibile LIFO) seguendo il flusso del programma, con scope statico, è stampato 0, mentre ci aspettavamo 1, essendo pluto l’ultimo blocco attivo
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 e scope dinamico sono uguali per la determinazione dell’ambiente locale e di quello globale gestione a run-time semplice ma poco efficiente(LIFO sulle attivazioni dei blocchi) flessibilità nei programmi difficile comprensione delle chiamate delle procedure
void pippo() {write(x);} void pluto() {const x=1; {const x=2; } stampa 1 con scope dinamico, perché considera la più recente associazione ancora attiva
problemi di scope statico dove introdurre le dichiarazioni qual è la visibilità delle variabili definizioni ricorsive begin const pippo = valore; const valore = 0; end valore usato prima della definizione begin const valore = 1; procedure pluto const pippo = valore; const valore = 0; end valore maschera la definizione esterna nella procedura
in ADA si usano dichiarazioni incomplete type lista = ^elemento; type elemento = record informazione: intero; successivo: lista end type elemento; type lista = access elemento; type elemento is record errore: elemento è usato prima di essere definito; in Pascal si può fare, ma solo con i puntatori in ADA si usano dichiarazioni incomplete
procedure pippo (A: integer); forward; procedure pluto (B: integer) begin pippo(3); end procedure pippo; pluto(4) in Pascal si usano definizioni incomplete per le funzioni mutuamente ricorsive; in C si può fare liberamente