La Torre di BaBasic - 2
La Torre di BaBasic - 2
In questa puntata ci occupiamo di uno dei costrutti dei linguaggi di programmazione più basici e, pertanto, presi per scontato: la variabile. Innanzitutto, ricordiamo che una variabile è (da un punto di vista logico) come una scatola, indicata con il nome scritto sull'etichetta, dove il programmatore mette un valore da memorizzare. Per esempio, il vostro videogioco preferito ha probabilmente una variabile chiamata BestScore dove conserva il punteggio più alto mai raggiunto, mentre il vostro word processor ricorderà il nome del documento che state editando in una variabile chiamata NomeFile.Diciamo che ad una variabile viene assegnato un valore quando infiliamo un nuovo valore nella scatola - eventualmente distruggendo quello vecchio che vi si trovava in precedenza.
Il modo in cui l'assegnamento viene ordinato dal programmatore varia leggermente da linguaggio in linguaggio. In Basic ed in linguaggio C si scrive
Pippo = 10
In Pascal e in Ada scriveremo invece
Pippo := 10;
mentre in Apl useremmo la forma
Pippo <- 10
Il concetto resta lo stesso.
Nel linguaggio macchina non esiste il concetto di variabile. I programmatori Assembler dunque non dispongono di variabili dove conservare dei valori: in Assembler però è possibile usare direttamente le locazioni di memoria della macchina.
Potete pensare alla memoria di un computer come a una serie molto grande di cellette di memoria, numerate dallo zero in su. Se il programmatore Assembler ha bisogno di memorizzare un valore usa una o più cellette tra quelle libere e vi salva il valore.
Mentre le cellette o locazioni sono numerate, le variabili hanno dei nomi che vengono scelti dal programmatore. Inoltre, in Assembler è il programmatore a dover trovare gli spazi in memoria, mentre con le variabili non c'è bisogno di pensarci: noi diremo che le variabili sono uno strumento di livello più alto rispetto alle locazioni.
In Assembler il programmatore può assegnare un nome alfabetico alle caselle, in modo che gli risulti più naturale identificarle per quello che contengono. Se al programmatore Assembler interessa compiere una operazione come quella che abbiamo visto per i linguaggi evoluti poco sopra, e cioè l'assegnamento ad una variabile chiamata Pippo del valore 10, egli innanzitutto assegnerà ad una locazione di memoria il nome Pippo, scrivendo per esempio:
PIPPO EQU 00 ; La locazione 00 si chiama Pippo
Questa riga di programma non genera codice: stabilisce solo l'equivalenza tra "Pippo" e la locazione numerata 0. In seguito il programmatore stiperà nella locazione il valore numerico 10.
MOVE #10, PIPPO
La realtà che sta sotto un programma Pascal, Basic, Lisp o Ada è sempre quella delle locazioni di memoria: torneremo tra poco su questo argomento osservando come questa realtà venga mascherata dal linguaggio a favore del costrutto logico più sofisticato, la variabile.
Quando assegnamo ad una variabile un valore diciamo che si crea un legame (in inglese, binding) tra i due.
Nel parlare di una variabile dobbiamo considerare quattro differenti aspetti che la descrivono. Sono chiamati valore, visibilità, vita e tipo della variabile, e sono fissati all'assegnamento, al momento del binding.
Iniziamo dal valore. Il legame di valore normalmente è dinamico, cioè può venire modificato: osservate la figura 1.
A := 10; In questo spazio esiste il legame tra la variabile A e il valore 10 A := 3;
Tuttavia, spesso è comodo avere delle variabili che mantengono lo stesso valore per tutta la durata del programma, le cosiddette costanti. Per esempio, in Pascal create una costante scrivendo,
Const Il_Mio_Nome = 'Luca Accomazzi';
Le costanti sono utili sia per dare chiarezza al programma, dando un nome significativo a un valore (per esempio, se stiamo scrivendo un programma che gestisce dischetti da 800 kB, è molto più chiaro trovare un riferimento al NumeroBlocchiSuDisco che al numero 1600), sia nel rendere più efficiente il programma evitando di memorizzare per molte volte lo stesso valore.
Notate che, nonostante il loro nome possa confondere, le costanti sono un tipo particolare di variabile: variabili con valore statico.
Per quanto riguarda il tipo della variabile, non c'è molto da dire. Da qualche parte nel programma si dichiara di che tipo è la variabile, e questo è tutto. Per esempio in Basic si può scrivere Nome$ = "Bit" ed in questo modo avete dichiarato che la variabile Nome è di tipo stringa (usando il simbolo del dollaro). In linguaggio C dichiarereste float NumeroReale per usare un numero con cifra decimale: il tentativo di assegnare a NumeroReale una stringa verrebbe bloccato dal compilatore.
In alcuni linguaggi, per esempio in Apl, è possibile cambiare il tipo di una variabile così come cambiereste il suo valore. Per esempio...
P <- 10 | Ora P è un intero. |
... |
|
P <- 3.1415926 | Ora P è un numero decimale. |
Veniamo alla visibiltà (scope). Non è bene che una variabile possa venire modificata da ovunque nel programma, nella parte principale come nelle routine che svolgono i compiti più semplici. In quel caso si ha una eccessiva proliferazione delle variabili, ed aumenta la probabilità che il programmatore ne introduca due o più con lo stesso nome, creando errori di difficile individuazione; inoltre, finiscono per trascinarsi per tutta la vita del programma una serie di variabili di scarsa rilevanza che occupano inutilmente memoria. Infine, e ben più importante, se tutte le variabili sono visibili da ogni punto del programma è praticamente impossibile creare programmi per moduli (secondo i metodi spiegati nella scorsa puntata).
Le regole che stabiliscono dove ed in che modo siano visibili le variabili sono sensibilmente differenti da linguaggio a linguaggio; nei più vecchi e superati (come il Fortran) non esistono neppure regole ben stabilite.
Prendiamo il caso del programma di figura 2: quando nella riga in neretto viene stampato il valore della variabile A, secondo voi apparirà sullo schermo il numero 10 o il numero 20? Il programma è scritto con la sintassi di Ada, ma possiamo facilmente pensare di averne degli analoghi in altri linguaggi.
Pascal scriverebbe 10, perché secondo le regole definite entro Pascal la procedura vede la variabile del programma principale. Apl scriverebbe 20, perché in quel linguaggio il riferimento ad una variabile con un dato nome viene collegato alla più recente creazione della variabile.
Program Prova is
Var A: constant Integer := 10;
Procedure Stampa is
begin
Write (A);
end Stampa;
Procedure Interna is
Var A: constant Integer := 20;
begin
Stampa;
end Interna;
begin
Interna;
end Prova;
Torniamo per un momento ad occuparci di cosa avviene nel computer, al livello del microprocessore e del suo codice macchina, prima di discutere della vita delle variabili.
Come abbiamo già spiegato nella scorsa puntata, un linguaggio è un programma complesso che trasforma i programmi da voi scritti in una forma equivalente, in codice macchina, che il computer può eseguire. Questo significa che nel vostro computer la memoria, composta da tante cellette, viene gestita dal compilatore del linguaggio. Se voi dichiarate in un programma che desiderate usare una variabile chiamata Pippo per contenere numeri interi, il compilatore riserva il numero adeguato di cellette ed esegue tutta una serie di operazioni per assicurare che voi non dobbiate accorgervi che effettivamente esiste questa realtà fisica nel vostro computer. Voi vi cimentate, programmando, solo con la astrazione logica di computer che il linguaggio vi lascia percepire.
Per chiarire questo punto, immaginiamo che voi abbiate creato una variabile Pippo di valore 10.
Pippo: integer := 10;
Se tentate successivamente di assegnare alla variabile Pippo il valore "A", una lettera quindi, il linguaggio segnala normalmente un errore (come abbiamo visto questo non accade in Apl).
Pippo := 'A' -- Errore!
Sarebbe possibile memorizzare in quella locazione il valore rappresentante la lettera "A", ma il compilatore segnala invece che è stato commesso un errore nel programma.
C'è dunque una sostanziale differenza tra la cella di memoria che fisicamente conserva il valore della variabile e la variabile. Quest'ultima è una entità logica. In Assembler la stessa operazione sarebbe considerata valida, perché in Assembler non esiste il concetto di variabile.
LDA #10
STA PIPPO ;Valido
LDA #'A'
STA PIPPO ;Valido anche questo!
Veniamo dunque alla vita della variabile. Indichiamo con questo nome la durata di tempo in cui alla variabile corrisponde una locazione di memoria, usata per memorizzarne il valore.
Nello scrivere un programma, come abbiamo visto la scorsa volta, si ricorre all'uso di procedure. In una procedura è possibile definire variabili locali, cioè variabili ad uso esclusivo della procedura: normalmente a queste variabili corrispondono locazioni di memoria solo quando il computer sta eseguendo il codice di quella procedura. La memoria necessaria viene allocata quando la procedura viene attivata, ed alla sua terminazione la memoria è liberata e riutilizzabile per altri scopi.
Vediamo in questo un'altra differenza fondamentale tra la variabile e la locazione di memoria che viene usata per realizzarla.
Se riflettete un momento potreste obbiettare che non c'è differenza tra la visibilità della variabile, di cui abbiamo parlato poco fa, e la sua vita, che abbiamo appena definito.
In effetti in molti linguaggi non c'è differenza pratica tra i due concetti. Prendiamo però il linguaggio C con le sue variabili static: osserviamo cosa accade quando viene dichiarata una variabile static in una funzione, così...
int UsiamoStatic ()
{
static int memoria; /* Dichiarazione */
memoria++; /* Incrementiamo il valore */
return (memoria); /* Restituiamo il valore */
}
Nel caso dell'esempio, il linguaggio C fornisce una variabile che è visibile solo entro la funzione (non possiamo accedere alla variabile dal resto del programma), ma che ha vita per tutta la durata del programma. La funzione dell'esempio restituisce al chiamante un numero intero, il numero delle volte che è stata chiamata.
Un simile effetto non è ottenibile nè nei linguaggi più semplici come il Basic ma neppure in molti linguaggi più evoluti come Pascal.
Notate che il linguaggio C garantisce che una variabile static viene creata con valore nullo (nel caso di un numero intero, vale zero): se così non fosse, l'utilità di una simile variabile sarebbe ben scarso. Provate, per vostro diletto, ad immaginare come sarebbe possibile scrivere una funzione che si comporta come la nostra UsiamoStatic se questo non fosse vero. E' molto difficile e richiede qualche trucco sporco.
Questa osservazione ci porta a riflettere su un punto oscuro nella natura delle variabili. Quale valore ha una variabile alla quale non è mai stato assegnato un valore? Ovvero, che numero appare sul video quando io eseguo la procedura seguente?
Procedure Mah is
Var Pluto: integer;
begin
write (Pluto);
end Mah;
Sono possibili tre alternative. La prima è quella seguita da Basic: la variabile ha un valore prestabilito (zero per gli interi): come abbiamo visto si comporta così anche il C con le variabili static. La seconda possibilità è quella seguita dalla maggioranza dei linguaggi: la variabile può avere qualunque valore, ed è pertanto un errore usarla prima di assegnargliene uno (alcuni compilatori se ne accorgono e segnalano un errore). Una terza possibilità non è implementata in nessun linguaggio che il sottoscritto conosca, poiché è molto dispendiosa, ma è la preferibile dal punto di vista teorico: la variabile ha un valore speciale 'indefinito', e quando si esegue una operazione usando anche una sola variabile che vale 'indefinito' il risultato è sempre 'indefinito'.
Questo ci porta all'argomento delle eccezioni, che tratteremo nelle prossime puntate.