Sei sulla pagina 1di 483

e e

i d i d

u o r

u o r
i t o r

i t o r
g

g
l i e l i e

A. Chianese, V. Moscato, A. Picariello ALLA SCOPERTA DEI FONDAMENTI DELLINFORMATICA


Questo testo uno strumento didattico utile per la comprensione
dei principali metodi ed algoritmi di Analisi Numerica e delle
problematiche connesse alluso di un elaboratore nella risoluzione
di un problema di matematica applicata. Gli argomenti trattati
riguardano: lanalisi degli errori, la risoluzione di sistemi lineari
e di equazioni non lineari, linterpolazione e lo smoothing di dati,
lapprossimazione di integrali e la risoluzione numerica di equazioni
differenziali e il sistema interattivo MATLAB. Il testo corredato
di numerosi esempi ottenuti implementando gli algoritmi esposti
in ambiente MATLAB.

Alessandra D'Alessio docente di Calcolo Numerico presso la Facolt di


Ingegneria dellUniversit di Napoli.
ANGELO CHIANESE
VINCENZO MOSCATO
ANTONIO PICARIELLO

Alla scoperta dei fondamenti


dellinformatica
Un viaggio nel mondo dei BIT

COD. U

35,00

e e
i d i d
u o r

u o r
i t o r

i t o r
g

g
l i e l i e
MANUALI
Angelo Chianese, Vincenzo Moscato, Antonio Picariello

Alla scoperta dei fondamenti


dellinformatica
Un viaggio nel mondo dei BIT

Liguori Editore
Questa opera protetta dalla Legge sul diritto dautore
(Legge n. 633/1941: http://www.giustizia.it/cassazione/leggi/l633_41.html).
Tutti i diritti, in particolare quelli relativi alla traduzione, alla citazione, alla riproduzione
in qualsiasi forma, alluso delle illustrazioni, delle tabelle e del materiale software a corredo,
alla trasmissione radiofonica o televisiva, alla registrazione analogica o digitale,
alla pubblicazione e diffusione attraverso la rete Internet sono riservati, anche nel caso
di utilizzo parziale.
La riproduzione di questa opera, anche se parziale o in copia digitale, ammessa
solo ed esclusivamente nei limiti stabiliti dalla Legge ed soggetta allautorizzazione scritta
dellEditore.
La violazione delle norme comporta le sanzioni previste dalla legge.
Il regolamento per luso dei contenuti e dei servizi presenti sul sito della Casa Editrice Liguori
disponibile al seguente indirizzo: http://www.liguori.it/politiche_contatti/default.asp?c=legal
Lutilizzo in questa pubblicazione di denominazioni generiche, nomi commerciali e marchi
registrati, anche se non specificamente identificati, non implica che tali denominazioni
o marchi non siano protetti dalle relative leggi o regolamenti.

Liguori Editore - I 80123 Napoli


http://www.liguori.it/

2008 by Liguori Editore, S.r.l.


Tutti i diritti sono riservati
Prima edizione italiana Marzo 2008

Chianese, Angelo :
Alla scoperta dei fondamenti dellinformatica. Un viaggio nel mondo dei BIT/
Angelo Chianese, Vincenzo Moscato, Antonio Picariello
Napoli : Liguori, 2008
ISBN-13 978 - 88 - 207 - 4305 - 5

1. Programmazione strutturata 2. Linguaggio C, MATLAB I. Titolo

Aggiornamenti:
16 15 14 13 12 11 10 09 08 10 9 8 7 6 5 4 3 2 1 0
Indice

PREFAZIONE

CAPITOLO PRIMO

1. LINFORMAZIONE E LE SUE RAPPRESENTAZIONI ....1


1.1. Linformatica ed il mondo moderno.......................................................1
1.1.1. Una definizione di Informatica..............................................................2
1.1.2. La rappresentazione dellinformazione .................................................2

1.2. La rappresentazione digitale ...................................................................5


1.2.1. I numeri in binario.................................................................................6
1.2.2. La rappresentazione dei numeri relativi ..............................................15
1.2.3. La rappresentazione dei numeri reali...................................................20

1.3. Gli operatori booleani ............................................................................26

1.4. La convergenza digitale .........................................................................27


1.4.1. La codifica delle informazioni testuali ................................................31
1.4.2. La codifica delle immagini..................................................................35
1.4.3. Immagini in movimento o video .........................................................40
1.4.4. La codifica del suono ..........................................................................42

1.5. Dati e metadati .......................................................................................43

CAPITOLO SECONDO

2. IL MODELLO DI ESECUTORE......................................45
VI

2.1. Processi e processori ..............................................................................45

2.2. Modello di Von Neumann......................................................................46


2.2.1. Le memorie .........................................................................................48
2.2.2. La CPU................................................................................................51
2.2.3. I bus.....................................................................................................53
2.2.4. Il clock.................................................................................................55

2.3. Firmware, software e middleware ........................................................57

2.4. Evoluzione del modello di Von Neumann ............................................61

2.5. Il modello astratto di esecutore .............................................................63

2.6. I microprocessori....................................................................................67

2.7. Un modello di processore.......................................................................74


2.7.1. La programmazione in linguaggio assemblativo.................................79

CAPITOLO TERZO

3. ALGORITMI E PROGRAMMI.........................................97
3.1. Informatica come studio di algoritmi ...................................................97
3.1.1. La soluzione dei problemi ...................................................................97
3.1.2. La calcolabilit degli algoritmi............................................................99
3.1.3. La trattabilit degli algoritmi.............................................................103

3.2. La descrizione degli algoritmi .............................................................105


3.2.1. Sequenza statica e dinamica di algoritmi ..........................................107

3.3. I linguaggi di programmazione...........................................................110

3.4. I metalinguaggi.....................................................................................111

3.5. La programmazione strutturata .........................................................113


3.5.1. La progettazione dei programmi di piccole dimensioni ....................117
3.5.2. La documentazione dei programmi ...................................................121

CAPITOLO QUARTO

4. LA STRUTTURA DEI PROGRAMMI ...........................123


Indice VII

4.1. Le frasi di un linguaggio di programmazione....................................123


4.1.1. Le dichiarazioni.................................................................................123
4.1.2. Le frasi di commento.........................................................................124
4.1.3. Listruzione di calcolo ed assegnazione ............................................124
4.1.4. I costrutti di controllo ........................................................................125

4.2. La potenza espressiva ..........................................................................130

4.3. La modularit.......................................................................................135
4.3.1. La parametrizzazione del codice .......................................................137
4.3.2. Le funzioni ........................................................................................140
4.3.3. La visibilit........................................................................................142
4.3.4. L'allocazione dinamica ......................................................................146
4.3.5. La ricorsione......................................................................................149
4.3.6. Gli effetti collaterali ..........................................................................149
4.3.7. Il riuso dei sottoprogrammi ...............................................................151
4.3.8. L'information hiding..........................................................................151

CAPITOLO QUINTO

5. I DATI ...........................................................................153
5.1. Informazione e dato .............................................................................153

5.2. La classificazione dei tipi .....................................................................156

5.3. I tipi atomici..........................................................................................157


5.3.1. Il tipo booleano..................................................................................157
5.3.2. Il tipo carattere ..................................................................................159
5.3.3. Il tipo intero.......................................................................................160
5.3.4. Il tipo reale ........................................................................................161
5.3.5. Il tipo per enumerazione....................................................................163
5.3.6. Il tipo subrange..................................................................................163

5.4. I tipi strutturati ....................................................................................164


5.4.1. Gli array.............................................................................................166
5.4.2. Il tipo stringa di caratteri ...................................................................169
5.4.3. Il record .............................................................................................171

5.5. I puntatori.............................................................................................172

5.6. I file........................................................................................................174
5.6.1. I file sequenziali ................................................................................174
5.6.2. I file di caratteri o textfile..................................................................177
5.6.3. La connessione e sconnessione dei file .............................................179
VIII

5.6.4. I file ad accesso diretto (RANDOM).................................................181

5.7. Lastrazione sui dati.............................................................................181


5.7.1. Il tipo pila ..........................................................................................182
5.7.2. Il tipo coda.........................................................................................182
5.7.3. Il tipo tabella......................................................................................183

CAPITOLO SESTO

6. IL LINGUAGGIO C.......................................................185
6.1. Introduzione .........................................................................................185

6.2. Le caratteristiche generali del linguaggio C ......................................185


6.2.1. Il vocabolario del linguaggio.............................................................186
6.2.2. Separatori ed identificatori ................................................................186
6.2.3. I simboli speciali ...............................................................................187
6.2.4. Parole chiavi......................................................................................188
6.2.5. I delimitatori......................................................................................188
6.2.6. Le frasi di commento.........................................................................188
6.2.7. Le costanti .........................................................................................189
6.2.8. Le stringhe.........................................................................................191

6.3. Il programma e la gestione dei tipi in C .............................................191


6.3.1. Lintestazione di una funzione ..........................................................194
6.3.2. Il blocco di una funzione ...................................................................194
6.3.3. I tipi semplici.....................................................................................195
6.3.4. Dichiarazione di variabili ..................................................................196
6.3.5. Alias di tipi ........................................................................................198
6.3.6. Il tipo enumerativo ............................................................................198
6.3.7. I tipi derivati ......................................................................................198
6.3.8. Il tipo array ........................................................................................198
6.3.9. Il tipo struct .......................................................................................199
6.3.10. Il tipo unione.................................................................................200
6.3.11. I campi ..........................................................................................200
6.3.12. Stringhe di caratteri ......................................................................201
6.3.13. I Puntatori .....................................................................................201

6.4. Gli operatori del linguaggio.................................................................202


6.4.1. Operatori Aritmetici ..........................................................................202
6.4.2. Operatori Relazionali ........................................................................202
6.4.3. Operatori Logici ................................................................................202
6.4.4. Operatori di incremento e decremento ..............................................202
6.4.5. Operatori sui puntatori.......................................................................203
6.4.6. Operatori logici bit oriented ..............................................................204
Indice IX

6.5. La specifica dellalgoritmo in C ..........................................................204


6.5.1. Istruzioni di assegnazione..................................................................204
6.5.2. Richiamo di funzioni .........................................................................205
6.5.3. Costrutti selettivi ...............................................................................206
If-else ..........................................................................................................206
Switch-case .................................................................................................208
6.5.4. Costrutti Iterativi ...............................................................................209
Il ciclo while ...............................................................................................209
Il ciclo do-while ..........................................................................................210
Il ciclo for....................................................................................................211

6.6. Le librerie di funzioni ..........................................................................212


6.6.1. La gestione dellI/O...........................................................................213
Apertura file................................................................................................213
Lettura da file..............................................................................................214
Scrittura su file............................................................................................215
La gestione delle stringhe ...........................................................................216
Funzioni per il calcolo matematico .............................................................217

6.7. Gli algoritmi di base in C.....................................................................218


6.7.1. Lo scambio di valore .........................................................................218
6.7.2. Inserimento in un vettore...................................................................220
6.7.3. Eliminazione in un vettore.................................................................222
6.7.4. Eliminazione di una colonna da una matrice.....................................224
6.7.5. Eliminazione di una riga da una matrice ...........................................226
6.7.6. Ricerca sequenziale ...........................................................................228
6.7.7. Ricerca binaria...................................................................................230
6.7.8. La ricerca del valore massimo in un vettore......................................233
6.7.9. La posizione del valore minimo in un vettore ...................................235
6.7.10. Ordinamento di un vettore col metodo della selezione.................237

6.8. Esempi di programmi completi in C...................................................239


6.8.1. Gestione di un array ..........................................................................240
6.8.2. Gestione di un archivio......................................................................244

CAPITOLO SETTIMO

7. IL LINGUAGGIO DELLAMBIENTE MATLAB ............249


7.1. Caratteristiche del linguaggio .............................................................249
7.1.1. Il vocabolario del linguaggio.............................................................250
7.1.2. I separatori.........................................................................................251
7.1.3. Gli identificatori e le parole chiavi ....................................................251
7.1.4. I simboli speciali ...............................................................................252
Operatori Aritmetici....................................................................................252
X

Operatori Relazionali ..................................................................................253


Operatori Logici..........................................................................................253
I delimitatori ...............................................................................................253
7.1.5. Le frasi di commento.........................................................................253
7.1.6. Le costanti numeriche .......................................................................254
7.1.7. Le costanti stringhe di caratteri .........................................................254

7.2. La struttura del programma ...............................................................255


7.2.1. La dichiarazione e gestione dei tipi ...................................................255
Tipi Semplici: il tipo intero.........................................................................255
Tipi Semplici: il tipo reale ..........................................................................256
Tipi Semplici: il tipo char ...........................................................................256
Tipi Semplici: il tipo booleano....................................................................256
Tipi Strutturati: il tipo array ........................................................................256
Tipi Strutturati: il tipo record ......................................................................258
Tipi Strutturati: il tipo intervallo.................................................................259
Tipi Strutturati: il tipo file...........................................................................260
7.2.2. La dichiarazione di funzione .............................................................260
7.2.3. La specifica dellalgoritmo................................................................261
7.2.4. Assegnazione.....................................................................................261
7.2.5. Richiamo di funzione ........................................................................262
Funzioni predefinite ....................................................................................263
Funzioni per lI/O .......................................................................................263
Lettura da file di INPUT .............................................................................264
Scrittura su file di OUTPUT .......................................................................264
7.2.6. Gli enunciati di selezione ..................................................................265
Il costrutto IF-ELSE....................................................................................265
Il costrutto switch-case ...............................................................................266
7.2.7. Le strutture iterativa ..........................................................................268
Il while ........................................................................................................268
Il for ............................................................................................................269

7.3. Gli algoritmi di base in MATLAB ......................................................270


7.3.1. Lo scambio di valore .........................................................................270
7.3.2. Linserimento in un vettore ..............................................................272
7.3.3. Leliminazione in un vettore .............................................................273
7.3.4. Leliminazione di una colonna da una matrice..................................275
7.3.5. Leliminazione di una riga da una matrice ........................................276
7.3.6. La ricerca sequenziale .......................................................................277
7.3.7. La ricerca binaria...............................................................................280
7.3.8. Il valore massimo in un vettore .........................................................283
7.3.9. La posizione del valore minimo di un vettore ...................................285
7.3.10. Minimo e massimo in una matrice................................................286
7.3.11. Ordinamento di un vettore col metodo della selezione.................288
7.3.12. Ordinamento di un vettore col metodo del gorgogliamento .........290

7.4. Esercizi completi...................................................................................293


Indice XI

CAPITOLO OTTAVO

8. LA TRADUZIONE DEI PROGRAMMI..........................299


8.1. Introduzione .........................................................................................299

8.2. Il processo di traduzione......................................................................300


8.2.1. La compilazione ................................................................................301
8.2.2. Il collegamento ..................................................................................305
8.2.3. Il caricamento ....................................................................................307
8.2.4. Gli interpreti ......................................................................................308

8.3. La verifica della correttezza dei programmi......................................309

8.4. Gli ambienti integrati...........................................................................313


8.4.1. Lambiente DEV-C++.......................................................................313
8.4.2. Lambiente MATLAB.......................................................................319

CAPITOLO NONO

9. LA PROGRAMMAZIONE ORIENTATA AGLI OGGETTI


331
9.1. I limiti del paradigma procedurale.....................................................331

9.2. Introduzione al paradigma object oriented .......................................332


9.2.1. La nascita della programmazione ad oggetti .....................................335

9.3. I fondamenti della programmazione ad oggetti.................................336


9.3.1. Oggetti e classi ..................................................................................336
9.3.2. Oggetti software e classi come implementazioni di tipi di dato astratto
339
9.3.3. Il meccanismo dellereditariet .........................................................344
9.3.4. Polimorfismo e binding dinamico .....................................................346
9.3.5. Note di progetto.................................................................................348

CAPITOLO DECIMO

10. INTRODUZIONE AI SISTEMI OPERATIVI...............349


XII

10.1. Introduzione .........................................................................................349

10.2. Caratteristiche di un Sistema Operativo............................................352


10.2.1. Levoluzione storica dei Sistemi Operativi...................................354
10.2.2. Alcuni esempi di Sistemi Operativi ..............................................356
DOS ............................................................................................................357
UNIX e LINUX ..........................................................................................357
OS/2 ............................................................................................................357
WINDOWS.................................................................................................357
Mac OS .......................................................................................................358

10.3. Larchitettura dei Sistemi Operativi ..................................................358


10.3.1. La gestione dei processi................................................................360
10.3.2. La gestione della memoria............................................................363
10.3.3. Il file system .................................................................................364
10.3.4. Linterprete dei comandi: la shell .................................................365

CAPITOLO UNDICESIMO

11. LE RETI DI COMUNICAZIONE ................................367


11.1. I sistemi di comunicazione...................................................................367
11.1.1. Codici e codifica...........................................................................368
11.1.2. Il problema degli errori.................................................................369
11.1.3. La trasmissione dellinformazione................................................370
11.1.4. I mezzi trasmissivi ........................................................................371

11.2. Le reti di calcolatori .............................................................................373


11.2.1. Tipologie di reti di calcolatori ......................................................375
11.2.2. Cenni allInternetworking.............................................................382
11.2.3. Aspetti software delle reti di calcolatori .......................................382
11.2.4. Il modello Internet.....................................................................386
11.2.5. La struttura di una rete TCP/IP .....................................................387
11.2.6. Le applicazioni di una rete TCP/IP...............................................390

CAPITOLO DODICESIMO

12. IL MONDO DI INTERNET.........................................393


12.1. Introduzione .........................................................................................393
12.1.1. Una ragnatela di connessioni ........................................................395
12.1.2. Internet ed il modello di comunicazione.......................................397
Indice XIII

12.1.3. La storia di Internet.......................................................................398

12.2. Il World Wide Web..............................................................................401


12.2.1. Nuovi standard per il Web............................................................404
12.2.2. I browser.......................................................................................405
12.2.3. La ricerca dellinformazione ed i motori di ricerca ......................405
Indici di rete ................................................................................................406
Motori di ricerca .........................................................................................406
12.2.4. I Portali .........................................................................................408

12.3. Internet ed il mondo del business........................................................409


12.3.1. Le-commerce...............................................................................410
12.3.2. Le-banking ..................................................................................410
12.3.3. Le-trading....................................................................................411
12.3.4. Le-procurement ...........................................................................411
12.3.5. Internet e la Pubblica Amministrazione: le-governement ...........412
12.3.6. Internet ed il mondo della formazione: le-learning .....................413

CAPITOLO TREDICESIMO

13. BASI DI DATI E SISTEMI INFORMATIVI.................417


13.1. Sistemi Informativi e Sistemi Informatici ..........................................417
13.1.1. Sistemi Informativi aziendali: levoluzione storica ......................419

13.2. Analisi e progettazione di un Sistemi Informativo ............................420


13.2.1. Le basi di dati ...............................................................................422
13.2.2. I Data Base Management Systems................................................422
13.2.3. Levoluzione dei DBMS...............................................................425
13.2.4. Le funzionalit di un DBMS.........................................................426
13.2.5. Ricerche in un database: introduzione al linguaggio SQL............427

APPENDICE A

14. VADEMECUM DEI PRINCIPALI TERMINI USATI IN


INFORMATICA....................................................................429
14.1. Fasce di Computer ...............................................................................429

14.2. Componenti Interni di un Computer..................................................430

14.3. I dispositivi di input e output ..............................................................433


XIV

APPENDICE B

15. ESERCIZI DI PROGRAMMAZIONE.........................437


15.1. Premessa ...............................................................................................437

15.2. Esercizi sulle variabili non strutturate ...............................................438

15.3. Esercizi sui vettori ................................................................................439

15.4. Esercizi sulle matrici ............................................................................442

15.5. Esercizi sui record ................................................................................447

15.6. Esercizi sulle stringhe ..........................................................................449

15.7. Esercizi sui file ......................................................................................453

INDICE DEI TERMINI ................................. 455

GLI AUTORI ............................................... 461


a Ilaria, Miriam e Susanna
a Marco
e a Vinni

e a chi non esiste,


ed bello pensare che c
Prefazione

Il titolo Alla scoperta dei fondamenti dellinformatica non stato scelto


con facilit. Per condividerlo gli autori hanno discusso a lungo e tra i tanti titoli sui
quali hanno litigato, lo hanno preferito perch sottolinea i diversi obiettivi che si
erano prefissi quando hanno iniziato la loro avventura di docenti in corsi di base
dellinformatica.
Negli ultimi anni il mondo dellinformatica ha vissuto frenetici
cambiamenti indotti dalle innovazioni tecnologiche imposte dalle esigenze di una
societ che ha scoperto nella conoscenza, e nella sua gestione, una primaria
occasione di competizione in un mercato sempre pi globalizzato. Non un caso
che molte delle tecnologie che hanno caratterizzato momenti della nostra vita sono
diventate rapidamente vecchie, inutilizzabili, o come si dice obsolete. Tanti
possono essere gli esempi di oggetti tecnologici che hanno mostrato una vita
brevissima se confrontata con la storia delluomo o con altri aspetti della societ.
Da archeologi appassionati della materia che proviamo ad insegnare abbiamo
selezionato quei concetti di base che vivono senza rischiare di arrugginire con il
tempo che passa. Non sappiamo se ci siamo riusciti, ma le nostre diverse
esperienze di insegnamento ci hanno fatto convergere su un testo che crediamo
possa rispondere alle aspettative. A dimostrazione di tale tesi nel libro sono
presenti sia il linguaggio C che quello di MATLAB per ribadire uno dei
fondamenti dellinformatica, la tesi di Church, che viene spesso dimenticata per
dare spazio ad esigenze di mercato o a mode: saper progettare un programma di
gran lunga pi appassionante del saper codificare un algoritmo in un linguaggio di
programmazione.
Il titolo vuole sottolineare anche il desiderio di insegnare senza annoiare,
coinvolgendo il lettore in un viaggio alla scoperta di un una materia di cui molti
parlano dilungandosi solitamente su temi secondari, specifici, o semplicemente
tecnologici. Il libro non un tradizionale testo di fondamenti di informatica, n uno
di preparazione al conseguimento del patentino europeo. Ha la pretesa di volersi
rivolgere a tutte le persone curiose di capire cosa si nasconde dietro un sipario fatto
di tanta tecnologia, ma soprattutto di tanto lavoro di persone, gli informatici, capaci
di trasformare idee in soluzioni, esigenze in applicazioni, realt in immaginario
della realt. E cerca di farlo camminando su un filo sottile teso tra due esigenze
difficili da coniugare: la precisione scientifica importante per insegnare e il
raccontare con lintenzione di appassionare. Abbiamo cos scoperto che un viaggio
nella storia dellinformatica poteva essere il collante giusto. E il libro si presenta al
XVIII

suo lettore proprio come un viaggio attraverso le tappe pi significative di un


cammino recente: dalla introduzione del codice binario, allaffermazione del
modello architetturale di Von Neumann, alla introduzione dei linguaggi di
programmazione, per terminare con le diffuse applicazioni tra cui i Sistemi
Operativi ed Internet. Il libro si presta ad essere letto in modi diversi a seconda
degli obiettivi didattici. Ad esempio per i corsi di Fondamenti di Informatici per
lIngegneria Informatica si consigliano i capitoli I, II, III, IV, V, VI, VIII e X;
mentre per i corsi di Elementi di Informatica per i non informatici si consigliano i
capitoli XII, II (senza microprocessori), I, X, VIII, VII, XIII (per i corsi di
ingegneria gestionale).
Come autori abbiamo una grande consapevolezza sorretta dai cambiamenti
intervenuti nella filiera di produzione del mondo delleditoria. I libri non sono pi
blocchi granitici difficili da modificare. Oggi possono seguire non solo le
esperienze degli autori, ma soprattutto dei lettori che con le loro indicazioni
possono contribuire a miglioramenti e adeguamenti. Non un caso che il materiale
da cui siamo partiti, stato per diversi anni sperimentato in corsi universitari.
Anche il presente libro sar sottoposto a continue revisioni per le quali ci
auguriamo che forte sia il coinvolgimento costruttivo principalmente dei nostri
studenti. Ai quali va sin da ora il nostro ringraziamento.
A tal fine ci preme sottolineare che la trattazione degli argomenti in un libro per la
didattica frutto dellesperienza che gli autori hanno maturato grazie al confronto
con il mondo circostante.
Come non ringraziare chi prima stato nostro maestro e chi poi ha
collaborato con noi; ma anche tutti i colleghi con i quali abbiamo discusso e con i
quali continuiamo a condividere la passione per linformatica. Il nostro primo
pensiero va al professore Bruno Fadini che oggi non pi tra noi: ci fa piacere
ricordarne la grande passione per la ricerca e la didattica che ci ha contagiato
rendendo il nostro lavoro appassionante. Un particolare ringraziamento ci sembra
giusto rivolgerlo a Nello Cimitile che per primo ci ha insegnato i principi della
programmazione strutturata: il sogno di alcuni di noi poter scrivere un libro con
lui spiegando il mondo dellinformatica con il semplice concetto di tipo. Infine un
ringraziamento tutto particolare va al professore Nino Mazzeo per il grande lavoro
che sta attualmente facendo per affermare il ruolo positivo dellinformatica: ci
aspettiamo da lui che le discussioni di questo ultimo periodo continuino con la
stessa partecipazione e piacere reciproco. I collaboratori da ringraziare sono tanti
per la loro presenza costante alle attivit del gruppo di ricerca sui sistemi
informativi multimediali, ma una citazione specifica va ad Antonio dAcierno e
Antonio Penta.
Non abbiamo bisogno di ringraziare lamico e maestro Lucio Sansone:
ormai parte di noi e con noi procede sul lungo cammino della didattica e della
ricerca.
Infine intendiamo dedicare anche il presente libro a tutti i docenti e
ricercatori universitari che come noi vivono nellUniversit credendo che la
didattica e la ricerca sono importanti per lo sviluppo di una societ pi giusta. E gli
autori hanno scritto il libro principalmente perch amano il proprio lavoro di
docenti e ricercatori.
va2
Capitolo primo

Linformazione e le sue rappresentazioni

1.1. Linformatica ed il mondo moderno


Lattuale societ moderna sempre pi dipendente dallinformatica. In molti
aspetti della vita quotidiana possibile avvertire la presenza di elaboratori
elettronici, il pi delle volte nascosti allinterno di altre macchine.
Le plurifunzionalit (agende, giochi, messaggi multimediali, etc) offerte dai
moderni telefoni cellulari, da sole, basterebbero a testimoniare la diffusione del
suddetto fenomeno, favorita anche dallavvento di Internet che ha abbattuto ogni
tipo di barriera e distanza sociale, culturale e tecnologica, rendendo possibile a tutti
laccesso a informazioni e servizi distribuiti in diverse parti del globo terrestre.
I moderni elaboratori, attraverso linsieme delle applicazioni software
(sistema operativo pi programmi) in esso contenute, mettono a disposizione
dellutente finale una vasta gamma di funzionalit in grado di adattarsi e rispondere
alle sue pi disparate esigenze del mondo del lavoro, intrattenimento o altro.
Grazie poi alla presenza delle interfacce grafiche, sempre pi user-friendly (facili
da usare per i suoi utilizzatori o utenti), di cui sono dotati i recenti sistemi operativi
e le applicazioni moderne, lutilizzo degli elaboratori stessi diventato sempre pi
semplice, accattivante ed intuitivo, suscitando un notevole interesse soprattutto
nelle generazioni pi giovani. Gi dalle scuole elementari i ragazzi iniziano ad
utilizzare il computer per giocare ai videogiochi, navigare su Internet, ascoltare
canzoni, scrivere documenti, vedere film, leggere la posta elettronica e cos via.
Molte delle attivit del mondo contemporaneo hanno risentito della
preponderante invasione degli elaboratori. Ad esempio sia nelle aziende che negli
uffici pubblici tutto il flusso (workflow) dei documenti (produzione, trasferimento,
archiviazione) avviene oramai mediante lutilizzo di elaboratori in un formato
immateriale detto digitale; nei pi moderni studi medici e aziende ospedaliere, la
gestione dei pazienti avviene attraverso cartelle cliniche digitali; nelle industrie
la maggior parte dei processi logistici, amministrativi e di produzione sono
automatizzati grazie alluso di elaboratori; nel settore dei trasporti aereo e
ferroviario risulta da anni possibile prenotare ed acquistare biglietti on-line;
analogamente in settori paralleli stanno sempre pi aumentando le aziende, i
negozi e gli enti che, attraverso siti Internet, consentono lacquisto di merci e
servizi (e-commerce); nel settore televisivo e dello spettacolo, grazie
allinformatica, che si stanno diffondendo sempre nuovi e pi sofisticati servizi
interattivi; parte del settore dellintrattenimento (sport, giochi, musica) stato, poi,
2 Capitolo primo

di fatto completamente rivoluzionato dagli elaboratori (videogiochi, software


musicali, moviole in campo); infine anche le pi moderne conquiste dellumanit
(viaggi spaziali, scoperte nel campo medico e scientifico, etc.) sono state in parte
rese possibili grazie allinformatica.
1.1.1. Una definizione di Informatica
La diffusa presenza degli elaboratori elettronici in tantissime e diversissime realt
sociali, ha determinato nella quotidianit la spontanea associazione tra il termine
Informatica e lutilizzo delle macchine informatiche.
In realt linformatica un campo di studio molto giovane. I primi elaboratori
risalgono infatti solo al 1946, ed allora, essa non era considerata una disciplina
separata dallelettronica e dalla matematica.
Nel corso degli anni, con lincremento delle capacit di calcolo (o
computazionale) degli elaboratori (anche detti per tali motivi computer), ci si
rese conto che il compito di programmare queste macchine era estremamente
difficile e richiedeva teorie e pratiche diverse da quelle dei campi esistenti.
Linformatica, ovvero la scienza della risoluzione dei problemi con laiuto degli
elaboratori, divenne quindi una disciplina autonoma.
Linformatica una disciplina complessa che abbraccia campi molto diversi
tra loro, in cui lelaboratore elettronico solo lo strumento mediante il quale si
possono attuare le tante applicazioni per esso pensate. Non a caso il termine
informatica deriva dalle parole informazione e automatica per sottolinearne
caratteristiche e finalit. Infatti linformatica soprattutto la scienza della gestione
e della elaborazione della informazione, e ci, spiega perch essa penetrata in
tutte le attivit umane rappresentando nei paesi pi evoluti linfrastruttura
fondamentale per lo sviluppo culturale ed economico della societ.
In tale ottica lelaboratore assolve al compito di gestire e memorizzare le
informazioni o dati, mentre lesperto informatico progetta le applicazioni
necessarie ad organizzare e quindi gestire le informazioni sia mediante luso di
macchine (tra le quali lo stesso elaboratore), sia facendo ricorso a sole risorse
umane, o infine a soluzioni miste in cui macchine ed uomini provvedono alla
attuazione dei processi individuati.
1.1.2. La rappresentazione dellinformazione
Affinch uninformazione venga correttamente memorizzata e gestita da un
elaboratore risulta fondamentale rappresentarla in una forma che sia ad esso
comprensibile. Risulta pertanto importante definire i concetti di informazione e
rappresentazione.
Spesso, si attribuisce al termine informazione un senso molto generico: esso
deriva da informare, ossia dare forma e fa riferimento ad un concetto astratto,
quanto mai vasto e differenziato, che pu, in linea del tutto generale, coincidere
con qualunque notizia o racconto. In termini molto semplici si pu dire che
l'informazione qualcosa che viene comunicato in una qualsiasi forma scritta o
orale.
Nella Teoria dell'Informazione l'informazione viene associata al concetto di
messaggio, anche se esso ha il solo compito di rappresentarla e trasportarla. In tal
caso, risulta evidente che, affinch un messaggio possa essere interpretato, mittente
Linformazione e le sue rappresentazioni 3

e destinatario debbano aver concordato un insieme di regole con le quali scrivere, e


in seguito leggere, il messaggio stesso. Inoltre perch esista informazione, il
messaggio deve contribuire ad eliminare incertezza: infatti, se una sorgente di
messaggi inviasse ripetutamente un solo simbolo, la quantit di informazione
ricevuta a destinazione sarebbe nulla. Infine i messaggi possono aumentare le
conoscenze se linformazione ricevuta si aggiunge a quelle preesistenti nel senso
che, dopo aver ricevuto un messaggio, si sa di pi rispetto alle situazioni
precedenti.
Come gi anticipato, perch persone o macchine possano utilizzare
uninformazione hanno bisogno che essa sia appropriatamente rappresentata. A
tale proposito, la storia delluomo ricca di esempi che testimoniano limportanza
della rappresentazione efficace delle informazioni: basti pensare che se non fosse
esistita la scrittura non avremmo un resoconto oggettivo degli avvenimenti
delluomo dalla sua nascita fino ad oggi. Del resto scrivere, leggere ed elaborare
informazioni implica che chi lo fa abbia preliminarmente concordato un codice,
ossia una serie di regole e convenzioni da seguire; e il mondo che ci circonda
ricco di esempi in campi anche molto diversi tra loro.
In generale esistono due modalit di rappresentazione. Nella prima, le
propriet del fenomeno rappresentato sono omomorfe alla forma della
rappresentazione. Essa viene detta analogica proprio perch la rappresentazione
varia in analogia con la grandezza reale: si pensi ad un termometro tradizionale, nel
quale la dilatazione del mercurio messa in relazione con la variazione rilevata di
temperatura. Nella rappresentazione analogica una grandezza rappresentata in
modo continuo e la gran parte delle grandezze fisiche della realt sono di tipo
continuo.
La seconda modalit di rappresentazione invece discreta, nel senso che si
utilizza un insieme finito di rappresentazioni distinte che vengono messe in
relazione con alcuni elementi delluniverso rappresentato. Una tale
rappresentazione unapprossimazione di quella analogica: infatti, se ad esempio
si prova a descrivere il colore del mare, ci si accorge che non si hanno nel
linguaggio naturale tanti termini quanti ne servirebbero per descrivere le infinite
sfumature percepite dai nostri occhi. La scelta del livello di approssimazione
dipende dalluso della rappresentazione discreta in quanto in molte applicazioni
reali non sempre hanno importanza tutti gli infiniti valori che linformazione reale
pu assumere. noto, ad esempio, a tutti che in una proiezione cinematografica
locchio umano percepisce il movimento per effetto della sovrapposizione
successiva di 24 fotogrammi in un secondo: non servirebbe a molto aggiungere
altre istantanee. In tale caso si sfrutta un limite dellutilizzatore della informazione
rappresentata per estrarre da un insieme infinito di valori un suo opportuno
sottoinsieme.
Noti ora i concetti di informazione e rappresentazione, ritornando al caso
degli elaboratori elettronici, in maniera pi dettagliata possibile affermare che
uninformazione per essere correttamente elaborata deve essere codificata in una
rappresentazione comprensibile allelaboratore stesso, e, a tale proposito, risultano
fondamentali i concetti di codifica e codice riportati di seguito.
La codifica linsieme di convenzioni e di regole da adottare per trasformare
uninformazione in una sua rappresentazione e viceversa. Si noti che la stessa
informazione pu essere codificata in modi diversi (rappresentazioni diverse) a
4 Capitolo primo

seconda del contesto: le rappresentazioni araba o romana dei numeri ne sono un


esempio (i simboli 1 e I costituiscono due codifiche diverse della stessa
informazione numerica).
Un codice un sistema di simboli che permette la rappresentazione
dellinformazione ed definito dai seguenti elementi:
- i simboli che sono gli elementi atomici della rappresentazione;
- lalfabeto che rappresenta linsieme dei simboli possibili: con cardinalit
(n) del codice si indica il numero di elementi dellalfabeto;
- le parole codice o stringhe che rappresentano sequenze possibili
(ammissibili) di simboli: per lunghezza (l) delle stringhe si intende poi il
numero di simboli dellalfabeto da cui ciascuna parola codice risulta
composta;
- il linguaggio che definisce le regole per costruire parole codici che
abbiano significato per lutilizzatore del codice.
La scrittura lesempio pi noto di codifica nella quale trovare facilmente un
esempio dei concetti indicati.
Siano allora V = {v1, v2, ..., vm } linsieme degli m valori diversi di una data
informazione e A = {s1, s2, ..., sn } un alfabeto composto da n simboli distinti. Si
considerino diverse lunghezze delle parole codice:
- con l = 1 si hanno tante parole codice diverse (n1) quanti sono i simboli
dellalfabeto;
- con l = 2 si hanno tante parole codice diverse quante sono le combinazioni
con ripetizione degli n simboli nelle due posizioni, ossia n2;
- con l = 3 si hanno n3 parole codice diverse.
In generale il numero di parole codice differenti uguale a nl. Ad esempio,
fissato lalfabeto A = {-,.} del codice Morse con n=2, si hanno le diverse
rappresentazioni riportate in tabella 1.

nl l=1 l=2 l=3 l=4


- -- --- ----
2 . -. --. ---.
.- -.- --.-
4 .. -.. --..
.-- -.--
.-. -.-.
..- -..-
8 -
.---
.--.
.-.-
.-..
..--
..-.
...-
16 .
Tabella 1 Esempi di codici Morse a lunghezza differente

Se la codifica deve mettere in corrispondenza biunivoca i valori


dellinformazione con le parole codice, ossia ad ogni vi deve corrispondere una ed
una sola sequenza s1i s2j ...sni , allora la lunghezza l deve essere scelta in modo che:
nl > m
Linformazione e le sue rappresentazioni 5

Si noti che nel caso di nl > m non tutte le configurazioni possibili (parole
codice) vengono utilizzate per la rappresentazione, come lesempio nella tabella 2
mostra nei primi due casi.

Informazione Suoi Valori Sue rappresentazioni


Giorni settimana luned, marted, mercoled, --- , --. , -.- , -.. , .-- ,
gioved, venerd, sabato, .-. , ..-
domenica
Colori semaforo rosso, giallo, verde -- , -., .-
Risposta si, no -,.

Tabella 2 Esempi di rappresentazione di informazioni diverse con codici a


lunghezza differente

Infine la codifica pu essere a lunghezza fissa o variabile. Nel primo caso


tutte le parole codice hanno sempre la stessa lunghezza fissata da particolari
esigenze applicative. La scrittura un caso, di contro, di codifica a lunghezza
variabile come possibile verificare in un qualsiasi vocabolario. I calcolatori
adottano codifiche a lunghezza fissata e definita.

1.2. La rappresentazione digitale


La scrittura alfabetica e le cifre della numerazione araba sono alcuni esempi tra le
numerose forme di quella che stata precedentemente definita rappresentazione
discreta.
Ai fini informatici assume particolare interesse la rappresentazione binaria
digitale basata su un alfabeto costituito da due soli simboli distinti, che assumono
convenzionalmente la forma di 0 e 1. Tali due simboli rappresentano le unit
minime di rappresentazione e memorizzazione digitale e vengono denominate bit
da binary digit.
Solitamente si indica con digitale la rappresentazione basata sui bit, anche se
essa teoricamente sottintende una rappresentazione con qualsiasi tipo di cifre.
Inoltre la diffusione dellinformatica nella societ ha comportato unestensione del
significato del termine ed oggi, nella sua accezione pi ampia, digitale assume il
significato di informazione codificata in contrapposizione con analogico che
invece descrive la realt nelle sue infinite forme e variet.
La rappresentazione digitale, sebbene implica ladozione di parole codici pi
lunghe per rappresentare una determinata quantit di informazioni, semplifica la
memorizzazione delle informazioni e rende i sistemi digitali meno soggetti ai
disturbi elettrici rispetto ai sistemi analogici.
Non a caso la rappresentazione delle informazioni allinterno dellelaboratore
si basa sullalfabeto binario {0,1} in quanto i supporti di memorizzazione delle
informazioni, i registri di memoria, vengono realizzati con componenti elementari
semplici detti flip-flop, che operano in due soli stati possibili.
In generale esistono tanti fenomeni diversi che possono essere facilmente
associati ad un bit:
- la presenza o assenza di tensione elettrica in un circuito elettrico;
6 Capitolo primo

- le polarit positiva e negativa di un magnete o di un supporto con


caratteristiche magnetiche tipo nastri e dischi;
- la presenza o lassenza di un buco su un supporto ottico come quello dei
cd-rom;
- lapertura o chiusura di una conduttura;
- la condizione di acceso o di spento di un interruttore.

Figura 1 Esempio di rappresentazione digitale di un segnale di tensione

La rappresentazione digitale anche pi affidabile (probabilit di errore


bassa), in quanto disturbi provenienti dallambiente o interferenze (rumore) indotte
da altri componenti difficilmente possono far variare lo stato di un componente che
memorizza i bit. Infatti adottando due soli stati si pu scegliere una separazione
massima tra le corrispondenti grandezze indicative dello zero e delluno, per cui il
rumore pur sommandosi non produce significative alterazioni.
La trasformazione delle grandezze analogiche nella loro rappresentazione
digitale ha tra i tanti vantaggi quello della fedelt della riproduzione e quello della
trasmissione dellinformazione a diversi tipi di dispositivi elettronici.
Alcune informazioni nascono gi in formato digitale grazie a strumenti che
operano direttamente con tale rappresentazione: tra essi il calcolatore elettronico
con le sue tante applicazioni, ma anche le moderne macchine fotografiche digitali ,
i telefoni cellulari, i registratori di suoni e video.
Per elaborare con un calcolatore delle grandezze reali di tipo continuo,
bisogna utilizzare la loro rappresentazione digitale con una approssimazione che
dipende dal processo di trasformazione in grandezze a valori discreti e dalla
precisione della rappresentazione digitale dei numeri.
1.2.1. I numeri in binario
Come gi anticipato, laritmetica usata dai calcolatori diversa da quella
comunemente utilizzata dalle persone ed utilizza il sistema binario poich pi
adatto ad essere maneggiato da circuiti elettronici.
facile osservare che il codice binario utilizza un alfabeto A = {0,1} con n=2.
Le informazioni numeriche vengono quindi rappresentate mediante stringhe di bit
di lunghezza l che producono 2l configurazioni (parole codice) diverse.
Viceversa se si devono rappresentare K informazioni diverse occorrono log2K
bit per associare ad esse codici diversi.
La precisione con cui i numeri possono essere espressi finita e
predeterminata, poich questi devono essere memorizzati con parole codice di
lunghezza fissata. Per ragioni legate alla costruzione dei moderni calcolatori,
d'uso fare riferimento a stringhe con l uguale ad 8 che vengono dette byte.
Sequenze di bit pi lunghe di un byte sono invece denominate word, la loro
lunghezza dipende dalle caratteristiche del sistema, ma sempre un multiplo del
Linformazione e le sue rappresentazioni 7

byte: 16, 32, 64 o 128 bit. Poich i calcolatori trattano con molti bit/byte sono state
introdotte le unit di misura binarie riportate in tabella 3.

Sigla Nome Numero byte Numero bit


B Byte 1 8
KB KiloByte 210=1024 8.192
MB MegaByte 220=1.048.576 8.388.608
GB GigaByte 230=1.073.741.824 8.589.934.592
TB TeraByte 240=1.099.511.627.776 8.796.093.022.208

Tabella 3 Unit di misura binarie

Con otto bit si rappresentano solo 28 (256) valori diversi. Nel caso in cui un
solo byte non fosse sufficiente per rappresentare i K valori dellinformazione,
allora si individua il numero b di byte tale che:

2 (b*8) > K

In altri termini, allinterno dei moderni calcolatori, la codifica a lunghezza


fissa ed adotta parole codice con una lunghezza che ha valori multipli di 8.

Numero di byte b Numero di bit (b*8) 2 (b*8) Configurazioni


1 8 28 256
2 16 216 65.536
3 24 224 16.777.216
4 32 232 4.294.967.296

Tabella 4 Valori di informazione rappresentabili al variare del numero di byte

Ladozione di stringhe a lunghezza finita e definita implica che i numeri


gestiti siano a precisione finita, ossia siano quelli rappresentati con un numero
finito di cifre, o pi semplicemente definiti allinterno di un prefissato intervallo di
estremi [min,max] determinati.
Nel mondo reale esistono esempi di sistemi di numerazione basati su numeri a
precisione finita come i sistemi di misura dei gradi degli angoli ([0, 360]) e del
tempo ([0, 60] per minuti e secondi e [0, 24] per le ore). In tali sistemi di
numerazione si introduce il concetto di periodicit, per cui valori non compresi
nellintervallo di definizione vengono fatti ricadere in esso. In generale, nei sistemi
di calcolo con numeri a precisione finita, le operazioni possono causare errori
quando il risultato prodotto non appartiene allinsieme dei valori rappresentabili. Si
dice condizione di underflow quella che si verifica quando il risultato
delloperazione minore del pi piccolo valore rappresentabile (min). Si chiama
overflow la condizione opposta, ossia quella che si verifica quando il risultato
delloperazione maggiore del pi grande valore rappresentabile (max). Infine il
risultato delloperazione non appartiene allinsieme quando non compreso
nellinsieme dei valori rappresentabili, pur non essendo n troppo grande n troppo
piccolo.
8 Capitolo primo

La tabella seguente mostra alcuni casi di overflow, di underflow e di non


appartenenza allinsieme di definizione per una calcolatrice decimale dotata di sole
tre cifre, con intervallo di definizione formato da numeri interi compresi
nellintervallo [-999,+999].

Operazione Condizione
200 +100 risultato rappresentabile
730 + 510 Overflow
-500-720 Underflow
2:3 risultato non rappresentabile

Tabella 5 Esempi di operazioni che generano risultati rappresentabili e non

Anche lalgebra dei numeri a precisione finita diversa da quella


convenzionale poich alcune delle propriet:
- propriet associativa: a + (b - c) = (a + b) c
- propriet distributiva: a (b - c) = a b a c
non sempre vengono rispettate in base allordine con cui le operazioni
vengono eseguite, come i casi seguenti mostrano usando la stessa calcolatrice di tre
cifre.

a b C a + (b - c) condizione (a + b) c Condizione
100 900 600 100 + ok (100 + 900) - 600 Overflow
(900 - 600)
a b C a (b - c) condizione abac Condizione
200 90 88 200 Ok 200 90 200 88 Overflow
(90 88)

Tabella 6 Applicazione delle propriet associativa e distributiva per algebre a


precisione finita

Lalgebra dei numeri a precisione finita deve essere gestita applicando i noti
criteri di periodicit e tenendo in considerazione le condizioni di errore indicate.
Per la periodicit i valori esterni allintervallo di definizione vengono ricondotti ad
esso prendendo il resto della divisione dei valori per il periodo.

intervallo periodo valore divisione resto


[0,360] 360 1200 1200 : 360 120
[0,60] 60 61 61 : 60 1
[0,60] 60 55 60 : 55 55
Tabella 7 Sistemi periodici

Le operazioni in sistemi periodici possono essere effettuate utilizzando la


sveglia della figura seguente che associa alle tacche i valori definiti
nellintervallo fissato. Per comodit di disegno si ristretto lintervallo di
definizione a [-7, 8]. La sveglia pu essere assimilata ad una calcolatrice che fa
somme e sottrazioni: basta posizionare la lancetta sul valore del primo operando,
spostarla di un numero di posizioni uguale al secondo operando in senso orario per
Linformazione e le sue rappresentazioni 9

la somma e in senso antiorario per la sottrazione, quindi leggere come risultato la


sua posizione finale. Si nota che, muovendo le lancette in senso orario, al valore
pi grande fa seguito quello pi piccolo; viceversa quello pi piccolo preceduto
da quello pi grande. Se a 6 si somma 8 si ottiene come risultato -2 e non 14.

Figura 2 Calcolatrice per un sistema di numerazione finita

Il sistema binario ha una importanza capitale in informatica in quanto


consente di rappresentare numeri mediante la combinazione di due soli simboli,
ovvero di codificare i numeri direttamente in bit, secondo la notazione interna dei
circuiti numerici. Inoltre allinterno dei calcolatori viene adottata unalgebra dei
numeri a precisione finita con un intervallo di definizione che dipende dal numero
di byte associato alla rappresentazione.
Poich le considerazioni che seguono non dipendono da tale intervallo, si
adotter una rappresentazione dei numeri che adotta un solo byte, solo per
semplicit degli esempi. La tabella seguente riporta alcuni numeri rappresentati in
binario.

1 0 1 0 0 1 0 1 165
0 0 0 0 0 0 0 1 1
1 1 1 1 1 1 1 1 255
0 0 0 0 0 0 0 0 0
Peso Peso Peso Peso Peso Peso Peso Peso
7 6 5 4 3 2 1 0
Tabella 8 Numeri in binario

Nel byte il bit pi a destra quello meno significativo (posizione o peso 0,


detto anche LSB da Least Significant Bit) mentre quello pi a sinistra quello pi
significativo (posizione o peso 7, detto anche MSB da Most Significant Bit).
Poich un byte pu rappresentare 28 valori diversi, si possono, ad esempio con
8 bit gestire i seguenti intervalli di numeri interi:
- [0, 255] (in binario [00000000,11111111])
- [-127, 128] (in binario [11111111,01111111])
10 Capitolo primo

entrambi costituiti da 256 numeri.


Un sistema di numerazione pu essere visto come un insieme di simboli
(cifre) e regole che assegnano ad ogni sequenza di cifre uno ed un solo valore
numerico.
I sistemi di numerazione vengono di solito classificati in sistemi posizionali e
non posizionali. Nei primi (un esempio il sistema decimale) ogni cifra della
sequenza ha unimportanza variabile a seconda della relativa posizione (nel sistema
decimale la prima cifra a destra indica lunit, la seconda le centinaia, etc), nei
secondi (un esempio dato dal sistema romano), di contro, ogni cifra esprime una
quantit non dipendente dalla posizione (nel sistema romano il simbolo L
esprime la quantit 50 indipendentemente dalla posizione).
possibile osservare che una data stringa di bit pu essere interpretata come
una qualsiasi sequenza di cifre in un sistema di numerazione posizionale che
associa alle cifre c un diverso peso in base alla posizione i occupata nella stringa
che compone il numero, dove il peso dipende dalla base b di numerazione:

ci bi + ci - 1 bi - 1 + ci - 2 bi - 2 + + c2 b2 + c1 b1 + c0 b0 + c-1 b-1
+ c-2 b-2 +

Nel caso dei numeri interi scompaiono le potenze negative della base e la
formula diventa:

ci bi + ci - 1 bi - 1 + ci - 2 bi - 2 + + c2 b2 + c1 b1 + c0 b0

Un sistema di numerazione posizionale quindi definito dalla base (o radice)


utilizzata per la rappresentazione. In un sistema posizionale in base b servono b
simboli per rappresentare i diversi valori delle cifre compresi tra 0 e (b-1).

Base Denominazione Valori delle cifre


10 Decimale 0123456789
2 Binaria 01
8 Ottale 01234567
16 Esadecimale 0123456789ABCDEF

Tabella 9 Sistemi posizionali

Poich basi diverse fanno uso delle stesse prime cifre, si rende necessario
distinguere le rappresentazioni dei numeri con un pedice indicante la base dopo
aver racchiuso la stringa tra parentesi.

(101111)2 = (142)5 = (47)10


Inoltre, poich nel sistema decimale la prima cifra a destra indica le unit, la
seconda indica le decine, la terza le centinaia, la quarta le migliaia, e cos di seguito
secondo le potenze del dieci, solo in esso possibile leggere i numeri come
tremilacentouno, unmilioneetrenta. Negli altri sistemi di numerazione devono
essere scandite le cifre, da quella di peso maggiore fino a quella di minor peso, con
indicazione della base (ad esempio unoquattrodue in base cinque). In tutte le
Linformazione e le sue rappresentazioni 11

basi gli zeri a sinistra possono essere omessi, cos come quelli a destra se il numero
dotato di virgola.
Nel passaggio da una base allaltra alcune propriet dei numeri si perdono: ad
esempio un risultato di una divisione pu essere periodico nella base dieci ma non
detto che lo sia in unaltra base, cos come la propriet di un numero di essere
divisibile per cinque ha senso solo se la base maggiore di cinque.
La introdotta interpretazione del numero secondo il sistema di numerazione
posizionale pesato consente di convertire nella base 10 il valore rappresentato in
una qualsiasi base b, calcolando la sommatoria dei prodotti delle cifre per i pesi:

l 1
Valore ci b i (1)
i 0
Ad esempio:

(101111)2 = 1 25+ 0 24+ 1 23+ 1 22+ 1 21+ 1 20= 32 + 8 + 4 + 2 +1 =

(142)5 = 1 52 + 4 51 + 2 50 = 25 +20 +2 =

(47)10 = 4 101 + 7 100

L'impiego nella base 2 di un minor numero simboli rispetto al sistema


decimale (2 contro 10) implica che lo stesso numero abbia una parola-codice pi
lunga in notazione binaria che non in quella decimale. Poich per rappresentare le
dieci cifre ci vogliono log210 bit ( 3,3 bit), solitamente la stringa di cifre in bit
approssimativamente tre volte pi lunga di quella decimale come lesempio
seguente mostra:

(1001101)2 = 1 26 + 0 25 + 0 24 + 1 23 + 1 22 + 0 21 + 1 20 =

= 64 + 0 + 0 + 8 + 4 + 0 + 1 =

= (77)10

In informatica, per evitare di dover trattare con stringhe di bit troppo lunghe,
sono stati introdotti il sistema ottale ed esadecimale. La tabella seguente mostra la
corrispondenza tra le cifre usate in tali rappresentazioni e i bit che le rappresentano.
Le numerazioni ottale ed esadecimale sono interessanti perch la
trasformazione di rappresentazioni di valori tra esse si e la base 2 (e viceversa)
immediata. Infatti la trasformazione di un valore da binario in ottale molto
semplice dato che una cifra del sistema ottale rappresentabile esattamente con tre
bit del sistema binario il cui valore uguale proprio alla cifra rappresentata.
La conversione avviene raggruppando le cifre binarie in gruppi di tre a partire
dalla posizione di peso minore. La conversione opposta ugualmente semplice:
ogni cifra ottale viene esplosa esattamente nelle tre cifre binarie che la
rappresentano.
12 Capitolo primo

Ottale Binario Esadecimale Binario


0 000 0 0000
1 001 1 0001
2 010 2 0010
3 011 3 0011
4 100 4 0100
5 101 5 0101
6 110 6 0110
7 111 7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111
Tabella 10 Rappresentazione cifre nei sistemi Ottale ed Esadecimale

La rappresentazione esadecimale ancora pi compatta: il processo di


conversione equivalente a quello binario-ottale ma le cifre binarie devono essere
raggruppate in gruppi di quattro.

Figura 3 Conversione tra Binario ed Ottale e Binario ed Esadecimale

Dagli esempi precedenti si visto che per convertire un valore da una


qualsiasi base b di numerazione al corrispondente valore nel sistema decimale, si
deve calcolare la somma delle potenze della base b assegnata.
Il procedimento inverso, cio quello da applicare per convertire un valore
decimale in un altro sistema di numerazione, viene mostrato per la conversione in
binario ma pu essere applicato per analogia con qualsiasi altra base di
numerazione.
Per convertire un numero decimale in binario si procede separando la parte
intera da quella decimale e applicando due procedimenti (algoritmi) diversi. Dato
un valore d decimale, lo si pu rappresentare come:

d = ci bi + ci-1 bi-1 + + c2 b2 + c1 b1 + c0 b0 + c-1 b-1 + c-2 b-2


+
Linformazione e le sue rappresentazioni 13

e scomporlo in parte intera e frazionaria. Nel caso di b=2:

dpi = ci 2i + ci-1 2i-1 ++ c2 22 + c1 21 + c0 20


dpf = c-1 b-1 + c-2 b-2 +

con: d = dpi + dpf

se si divide la parte intera per 2:

dpi /2 = ci 2i-1 + ci-1 2i-2 ++ c2 21 + c1 20 + c0 2-1

si ottiene:

dpi1= ci 2i-1 + ci-1 2i-2 ++ c2 21 + c1 20

con c0 resto della divisione.


Se ora si divide la parte intera ottenuta precedentemente dpi1 ancora per la base 2:
dpi1 /2 = ci 2i-2 + ci-1 2i-3 ++ c2 20 + c1 2-1

si ottiene:

dpi2 = ci 2i-2 + ci-1 2i-3 ++ c2 20

con c1 resto della divisione.


Si comprende allora che il procedimento deve essere ripetuto fino a quando si
ottiene un quoziente uguale a 0. Linsieme dei resti delle diverse divisioni
comporranno la stringa in binario del numero d, in particolare: il resto della prima
divisione corrisponde alla cifra binaria del bit meno significativo, quello
dellultima il bit pi significativo come lesempio che segue mostra.
ALGORITMO
1) dividere la parte intera del numero d
per la base b
2) scrivere il resto della divisione
3) se il quoziente maggiore di zero,
usare tale risultato al posto del numero
d di partenza e continuare dal punto 1)
4) se il quoziente zero, scrivere tutte le
cifre ottenute come resto in sequenza
inversa

Esempio 1 Algoritmo per la conversione della parte intera di un numero in base b nel
corrispondente decimale
14 Capitolo primo

Si noti che lalgoritmo nellesempio 1 consente di convertire un numero intero


in base dieci in una qualunque base b. Nel caso di b =2 si ottiene la conversione in
binario del numero assegnato.
Per la conversione della parte frazionaria si procede al contrario moltiplicando
per 2:

dpf 2 = c-1 b0 + c-2 b-1 +


dpf1 = c-2 b-1 +

per spostare c-1 a sinistra della virgola che diventa parte intera.

Anche in questo si continua a moltiplicare per 2 solo la parte frazionaria fino a


quando non si verifica una delle seguenti condizioni:
- la parte frazionaria dpfi-esima non si annulla;
- la parte frazionaria dpfi-esima si ripete con periodicit;
- ci si accontenta di una rappresentazione approssimata con un numero di
bit inferiore a quello che andrebbero calcolati per raggiungere una delle
condizioni precedenti. Si noti che solo la prima condizione garantisce una
conversione senza approssimazione.

ALGORITMO
1) moltiplicare la parte frazionaria
del numero d per la base b
2) scrivere la parte intera del
prodotto
3) se la nuova parte frazionaria del
prodotto diversa da zero o non
si ripete periodicamente, oppure
si non sono state determinate le
cifre binarie prefissate, usare
tale risultato al posto del
numero d di partenza e
continuare dal punto 1)
4) se la nuova parte frazionaria
verifica una delle tre condizioni
di terminazione, scrivere tutte le
cifre ottenute come parte intera
nellordine in cui sono state
calcolate

Esempio 2 Algoritmo per la conversione della parte frazionaria di un valore in base b


nel corrispondente decimale

Si noti che lalgoritmo consente di convertire un numero frazionario in base


dieci in una qualunque base b. Nel caso di b =2 si ottiene la conversione in binario
del numero assegnato.
Linformazione e le sue rappresentazioni 15

Ladozione della notazione posizionale pesata senza altra convenzione


(posizione della virgola o indicazione di un eventuale segno) consente di
interpretare una sequenza di bit come rappresentazione di un numero naturale. Su
tali numeri si applicano gli algoritmi di somma e sottrazione, prodotto e divisione
in modo del tutto analogo ai numeri in base 10, l'unica differenza risiede nella poca
pratica che si ha con essi.
Come nel decimale si definiscono la tavola delladdizione e la tabellina del
prodotto per le cifre binarie.

Tabella 11 Tabelle di somma e prodotto per le cifre binarie

Di seguito sono riportati alcuni esempi di operazioni: si noti che nella somma
si deve tener conto del riporto (che si propaga a sinistra cos come nellaritmetica
decimale) mentre nella sottrazione, in presenza delloperazione 0 1, si deve
attivare il prestito dalle cifre pi a sinistra (come nellaritmetica decimale).

Esempio 3 Esempi di somma, sottrazione e prodotto fra numeri binari

La rappresentazione finora presentata, che fa uso dellequazione (1) per la


codifica da binario a decimale e dellalgoritmo nellesempio 1 per la codifica da
decimale a binario, viene anche detta in binario puro. Essa rende possibile
rappresentare tutti i numeri interi positivi appartenenti allintervallo [0, 2l 1] con l
ad indicare i bit a disposizione.
1.2.2. La rappresentazione dei numeri relativi
Per i numeri relativi, ovvero tutti i numeri interi, positivi e negativi, incluso lo
zero, si utilizzano invece altre tipologie di rappresentazioni. A tale proposito,
nellevoluzione dellaritmetica binaria, sono state definite rappresentazioni diverse
16 Capitolo primo

(segno e modulo, complemento a uno, complemento a due, eccesso 2l - 1) per


cercare di realizzazione circuiti elettronici capaci di effettuare le operazioni
aritmetiche allinterno di un calcolatore in modo ottimizzato e allo stesso tempo
semplice.
Poich il segno assume due soli valori (+ oppure ), allora lo si pu
codificare con un singolo bit utilizzando il bit pi significativo per indicarlo: ad
esempio, 0 per indicare un valore positivo ed 1 per indicarne uno negativo.
Con l bit, l 1 di essi vengono attribuiti alla rappresentazione del valore assoluto
del numero, e il bit pi a sinistra (MSB) alla rappresentazione del segno.

Figura 4 Rappresentazione per segno e modulo

La rappresentazione, detta per segno e modulo, consente di codificare tutti i


numeri relativi appartenenti allintervallo:

[-2l -1
+ 1, 2l -1
- 1]

con 2l - 1 valori positivi e altrettanti negativi: per un totale di 2l valori diversi.


Per l =8 sono rappresentabili tutti i numeri relativi appartenenti allintervallo [-
127,127]. Si noti che, poich sono presenti due configurazioni dello zero, lo 0
positivo (00000000) e lo 0 negativo (10000000), le operazioni di somma e
sottrazione devono essere corrette nellattraversamento dello zero. Ma il motivo che
ha portato alla ricerca di una rappresentazione diversa per i numeri negativi, che la
rappresentazione per segno e modulo richiede un algoritmo complesso per effettuare somma e
sottrazione in presenza delle diverse combinazioni dei segni degli operandi.
Con la rappresentazione in complemento a due, somma e sottrazione si
possono effettuare con lo stesso algoritmo, e quindi si possono affidare allo stesso
circuito elettronico. In complemento a 2 le configurazioni che hanno il bit pi
significativo uguale a zero, cio quelle comprese nellintervallo [0, 2l - 1- 1],
rappresentano se stesse (numeri positivi), mentre le configurazioni col bit pi
significativo uguale a uno, cio quelle rientranti nellintervallo [2l - 1,2l - 1],
rappresentano i numeri negativi che si ottengono traslando a sinistra lintervallo di
2l, cio lintervallo [-2l - 1,-1].

Figura 5: Rappresentazione per complemento a 2


Linformazione e le sue rappresentazioni 17

Nella rappresentazione per complemento a 2, i valori rappresentati sono


compresi nellintervallo:

[-2l -1, 2l -1 - 1]

sono sempre 2 l :

- [0,2l -1] per i valori positivi


- [-2l - 1,-1] per i valori negativi.

lintervallo non simmetrico:

- 2l - 1 valore assoluto del minimo


- 2l -1 valore del massimo

ed esiste una sola rappresentazione dello zero.


Con 8 bit, ad esempio, si rappresentano i numeri naturali nellintervallo [0, 28-
1], cio [0, 255], oppure i numeri relativi nellintervallo [-27, 27-1], cio [-128,
127]. Con 16 bit (2 byte) si rappresentano i numeri naturali nellintervallo [0,216-1],
cio [0,65535], oppure i numeri relativi nellintervallo [-215, 215-1], cio [-32768,
32767].
Il complemento a due x di un valore negativo x si calcola sottraendo il valore
assoluto di x a 2l:

x = 2l |x|

Se si definisce il complemento alla base b di una cifra c come:

c = b -1 - c

allora il complemento a 2 si ottiene complementando alla base tutte le cifre


del valore assoluto del numero x e sommando poi 1 al valore ottenuto, come
lesempio 4 mostra nel caso di l = 8. Si noti che il complemento alla cifra nel caso
di b=10 si ottiene sottraendo la cifra c a 9, mentre nel caso di b=2 equivale alla
sostituzione dello zero con luno e delluno con lo zero.

Esempio 4 Calcolo del complemento a 2 di un numero x negativo


18 Capitolo primo

Viceversa, se si ha una sequenza di l bit che rappresenta un numero intero con


segno, con i numeri negativi rappresentati in complemento a 2, allora, per ottenere
il numero rappresentato, si procede nel seguente modo.
- Si esamina il bit di segno.
- Se esso zero, il numero rappresentato non negativo e lo si calcola con
la normale conversione binario-decimale.
- Se invece il bit di segno uno, allora si tratta di un numero negativo, per
cui, per ottenerne il valore assoluto, si applica lo stesso procedimento
visto in precedenza complementando tutti i bit e sommando 1 al risultato.
Unaltra interpretazione del complemento a 2 riporta che il bit di segno, quello
pi significativo nella stringa di bit, contribuisca con peso negativo alla
determinazione del valore nel sistema di numerazione posizionale pesato; in altri
termini, con l bit, con la prima posizione che parte da zero, si ha che il peso della
cifra pi significativa cl-1 -2l - 1:

cl - 1 (-2l -1
) + cl - 2 (2l -2
) + + c1 21 + c0 20

come lesempio che segue dimostra (sempre con l = 8).

Esempio 5 Interpretazione del complemento a 2

Il complemento a uno (x) del numero x si differenzia dal complemento a 2


(x) dello stesso numero per una unit:

x = x - 1

Dalla definizione si comprende che il complemento a 1 di un numero, detto


anche complemento diminuito o complemento alla base, si ottiene semplicemente
complementando tutte le cifre del numero.
Il complemento a 1 stato usato in alcuni calcolatori, ma stato abbandonato
perch alla semplicit di determinazione dei numeri negativi (nel caso di base 2
basta sostituire ad ogni uno lo zero ed ad ogni zero luno) accompagna una doppia
rappresentazione dello zero che complica le operazioni di somma e sottrazione. Va
notato che tale rappresentazione simmetrica come quella per segno e modulo: con
l bit lintervallo rappresentato [-(2l - 1-1), 2 l - 1-1].
Nella rappresentazione per eccesso 2l -1 i numeri negativi si determinano
come somma di se stessi con 2l -1 dove l il numero di bit utilizzati. Si noti che il
sistema identico al complemento a due con il bit di segno invertito. In pratica i
numeri compresi in [-2l - 1, 2l - 1-1] sono mappati tra [0, 2 l -1].
Linformazione e le sue rappresentazioni 19

Figura 6: Rappresentazione per eccessi

In tale rappresentazione, il numero binario che rappresenta 2l -1 sar associato


allo zero, mentre i valori minori di 2l - 1 ai numeri negativi e quelli maggiori a
quelli positivi. Nel caso di n = 8 i numeri appartenenti a [128, 127] sono mappati
nellintervallo [0, 255] (con i numeri da 0 a 127 considerati negativi, il valore 128
corrisponde allo 0 e quelli maggiori di 128 sono positivi).

Tabella 12 Esempi di codifica di numeri negativi nelle varie rappresentazioni

Le rappresentazioni in complemento a due ed eccesso 2l -1 sono le pi


efficienti per svolgere operazioni in aritmetica binaria poich permettono di trattare
la sottrazione tra numeri come una somma tra numeri di segno opposto:
(X - Y) = (X + (-Y))
Si noti che tale propriet ha validit solo nel caso di rappresentazioni finite dei
numeri come lesempio 6 dimostra. cos possibile costruire dei circuiti che fanno
solo addizioni.

Esempio 6 Operazioni in complementi a 2


20 Capitolo primo

1.2.3. La rappresentazione dei numeri reali


I numeri reali vengono rappresentati in binario attraverso la seguente notazione
scientifica:

con m numero frazionario detto mantissa, la base b numero naturale prefissato


ed e numero intero chiamato esponente o caratteristica. L'esponente determina
lampiezza dell'intervallo di valori preso in considerazione, mentre il numero di
cifre della mantissa determina la precisione del numero (ossia con quante cifre
significative sar rappresentato). Tale notazione scientifica viene adottata per
diversi motivi:
- la sua indipendenza dalla posizione della virgola;
- la possibilit di trascurare tutti gli zeri che precedono la prima cifra
significativa con la normalizzazione della mantissa;
- la possibilit di rappresentare con poche cifre numeri molto grandi oppure
estremamente piccoli;
- la dipendenza del valore rappresentato dalla mantissa e dallesponente se si
adottano specifiche convenzioni per la base e la mantissa.
La rappresentazione in binario dei numeri reali si caratterizza rispetto alla notazione
scientifica per alcune particolarit nel modo di rappresentare e utilizzare i numeri, dovute
all'uso di rappresentazioni finite e definite sia per l'esponente che per la mantissa. I due
fattori limitano quindi sia l'intervallo dell'insieme dei numeri reali che possibile
rappresentare, che il grado di precisione che essi avranno. Infatti in un intervallo reale
comunque piccolo esistono infiniti valori (i numeri reali formano un continuo).
I valori rappresentabili in binario appartengono invece ad un sottoinsieme che contiene
un numero finito di valori reali ognuno dei quali rappresenta un intervallo del continuo. In
altri termini, diviso l'insieme dei numeri reali in intervalli di fissata dimensione, si ha, come
la figura mostra, che ogni x appartenente all'intervallo [Xi, Xi+1[ viene sostituito con Xi.

Figura 7 Finitezza della rappresentazione numeri reali


Linformazione e le sue rappresentazioni 21

La sostituzione di un numero reale x con il valore X rappresentante l'intervallo


a cui x appartiene, pone notevoli problemi di approssimazione in tutti i calcoli che
usano valori del tipo reale.
Per valutare gli effetti delle approssimazioni e gli errori che ne possono
derivare, nata la disciplina chiamata calcolo numerico, che si pone come
obiettivo la ricerca di algoritmi appropriati per la soluzione di problemi matematici
che fanno largo uso dei numeri reali. Difatti un qualsiasi calcolo numerico sarebbe
privo di senso, qualora non si avesse un'idea del tipo e dell'entit degli errori che si
possono commettere.
In concreto i numeri reali rappresentabili in binario godono della seguente
propriet:

x X
Xi 1 Xi

dove rappresenta l'errore che si commette sostituendo x con X, dove:


- X = Xi se si approssima per difetto;
- X = Xi+1 se si approssima per eccesso.
Il valore dipende dalla rappresentazione finita (numero finito di cifre)
utilizzata per i numeri reali. Ad esempio disponendo di una calcolatrice con una
aritmetica a quattro cifre decimali che applica le note regole di arrotondamento
sull'ultima cifra, si ha:

NUMERO ARROTONDAMENTO ERRORE


0,00347 0,0035 3*10-5 = 0.3 *10-4
0,000348 0,0003 48*10-6 = 0.48*10-4
0,00987 0,0099 3*10-5 = 0.3 *10-4
0,000987 0,0010 13*10-6 = 0.13*10-4
Tabella 13 Errori di arrotondamento

con un errore massimo sull'ultima cifra di 0.5 (0.5 * 10-4) per le classiche
regole dell'arrotondamento. In generale se -m il peso della cifra meno
significativa, l'errore massimo che si commette :

1 m
10
2

Linsieme R anche costituito da infiniti valori ( definito dallintervallo ]-


, [). I numeri reali rappresentabili sono invece definiti in un insieme limitato con
estremi predefiniti [-minreal, maxreal]. Si definiscono:
- loverflow come la condizione che si verifica quando i valori o sono pi
piccoli di minreal o pi grandi di maxreal;
- lunderflow come la condizione che si verifica quando un valore, per
effetto delle approssimazioni, viene confuso con lo zero.
Si noti che la rappresentazione in virgola mobile, fissata la base, consente di
esprimere lo stesso valore con infinite coppie (mantissa, esponente), ad esempio:
48 103 uguale a 4800 100, ma anche a 4,8 102
22 Capitolo primo

allora possibile scegliere, tra le infinite coppie quella che preserva il


maggior numero di cifre significative con la normalizzazione della mantissa. Per
esempio, per i numeri minori di uno quando la cifra pi a sinistra uno zero, si
traslano (shift) verso sinistra le cifre diverse da zero (significative) decrementando
l'esponente di tante cifre quante sono le posizioni scalate: in questo modo si ottiene
unaltra coppia distinta, ma avente il medesimo valore del precedente (ad esempio
0,0025 *100 equivalente 2,5000 * 10-3). La mantissa scalata in questo modo
prende il nome di mantissa normalizzata e il numero in virgola mobile, il nome di
numero normalizzato. In generale la forma normalizzata della mantissa obbliga che
la sua prima cifra sia diversa da zero e che la sua parte intera sia in generale un
numero minore dalla base.
Ad esempio disponendo di una calcolatrice con le seguenti caratteristiche:
- rappresentazione con b = 10,
- cinque cifre per la mantissa considerata minore di 10,
- due cifre per l'esponente,
- rappresentazione normalizzata con la prima cifra diversa da zero.
si hanno le seguenti rappresentazioni normalizzate:

Numero Valore
0,384 3,8400 10-1
1345 1,3450 103
64350 6,4350 104
333 3,3300 102
0,0048 4,8000 10-3
0,0000001 1,0000 10-8

Tabella 14 Rappresentazioni Normalizzate

e la condizione di overflow quando:

x >9,9999 1099

e di underflow quando:

x < 1,0000 10-99

Osservando la modalit di rappresentazione in virgola mobile, si pu notare


che gli intervalli [Xi, Xi+1] non hanno tutti la stessa ampiezza a causa della finitezza
del numero di cifre della mantissa: man mano che ci si avvicina alla condizione di
overflow gli intervalli si fanno sempre pi ampi, mentre intorno alla condizione di
underflow non solo si addensano ma diventano sempre pi piccoli. Con la
calcolatrice precedente facile osservare il fenomeno confrontando gli intervalli
[1.0000 10-99, 1.0001 10-99] e [9.9998 1099, 9.9999 1099].
Con la rappresentazione in virgola mobile le operazioni non solo si
complicano ma possono generare errori di approssimazione. Ad esempio la somma
e la sottrazione richiedono l'allineamento degli esponenti:
100 100 + 100 10-2 = 100 100 + 1 100 = 101 100
Linformazione e le sue rappresentazioni 23

mentre per il prodotto e la divisione servono operazioni separate sulle


mantisse e sugli esponenti:

100 100 * 100 10-2 = (100 * 100) 10(0-2) = 10000 10-2

L'allineamento degli esponenti produce come effetto indesiderato quello di far


scomparire alcune cifre rappresentative del numero. Ad esempio la somma dei
numeri seguenti:

1,9099 101 + 5,9009 104

con la calcolatrice considerata prima, diventa:

0,0001 104 + 5,9009 104

con il troncamento delle cifre 9099 del numero con esponente pi piccolo.
Le operazioni che richiedono maggiore attenzione sono l'addizione e la
sottrazione. Infatti, la causa principale degli errori di calcolo numerico risiede nella
sottrazione di numeri di valore quasi uguale; in tal caso le cifre pi significative si
eliminano fra loro e la differenza risultante perde un certo numero di cifre
significative o anche tutte (fenomeno detto cancellazione). Altra causa di errori la
divisione per valori molto piccoli, poich il risultato pu facilmente superare il
valore di overflow: deve essere pertanto evitata non solo la divisione per lo zero
ma anche per valori ad esso prossimi.
Il grande vantaggio della rappresentazione in virgola mobile che, se si
conviene che le mantisse siano trasformate in valori minori di 10 con operazioni
interne, un numero reale pu essere rappresentato nella memoria di un calcolatore
con un numero intero indicativo della parte decimale della mantissa e con un altro
numero intero per l'esponente.
Per esempio il numero 0,1230 10-9 viene rappresentato con la coppia di
numeri interi (1230,-9) e gestito con operazioni interne che ne costruiscono
l'effettivo valore.
Nel caso binario la rappresentazione in virgola mobile normalizzata assume la
forma:

Nel 1980, i principali costruttori di elaboratori elettronici, producevano


calcolatori che utilizzavano i numeri float, ognuno con un proprio formato
numerico e con proprie convenzioni di calcolo. Nel 1985 divenne operativo lo
Standard 754 IEEE per i numeri in virgola mobile, e i maggiori costruttori
24 Capitolo primo

adottarono questo standard per i loro processori matematici. Lo standard 754


definisce principalmente tre formati numerici a virgola mobile:
- singola precisione o precisione semplice (32 bit),
- doppia precisione 64 bit),
- precisione estesa (80 bit).

Consideriamo i formati a 32 e 64 bit, che utilizzano la base 2 e la notazione in


eccesso per lesponente.

Figura 8 Formati a singola e doppia precisione

Nella stringa di bit, si succedono nellordine da sinistra (MSB) a destra


(LSB):
- un bit per il segno del numero complessivo, (zero per positivo ed uno per
negativo);
- otto bit nel caso della singola precisione (11 per la doppia precisione) per
l'esponente rappresentato per eccesso cos da non doverne indicare il
segno;
- 23 bit nel caso della singola precisione (52 per la doppia) per la mantissa.
La mantissa normalizzata per cui comincia sempre con un 1 seguito da
una virgola binaria, e poi a seguire il resto delle cifre. Lo standard prevede
lassenza sia del primo bit che del bit della virgola perch sono sempre
presenti: linsieme delluno implicito, della virgola implicita e delle cifre
esplicite prende il nome di significante.

Argomento Precisione singola Precisione doppia


32 Bit 64 bit
Bit del segno 1 1
Bit per lesponente 8 11
Bit per la mantissa 23 52
Cifre decimali mantissa Circa 7 (23/3.3) Circa 15 (52/3.3)
Esponente (rappresentazione) Base 2 ad eccesso 127 base 2 ad eccesso 1023
Esponente (valori) [-126, 127] [-1022, 1023]

Tabella 15 Caratteristiche formati singola e doppia precisione


Linformazione e le sue rappresentazioni 25

Lesempio di seguito riepiloga alcuni dei procedimenti illustrati per la


rappresentazione di numeri da binario a decimale e viceversa.

Problema Soluzione
Si vuole convertire il Poich la rappresentazione binaria pura consente la
numero 16 in binario codifica di numeri interi postivi allora 16
utilizzando una codificabile attraverso questa rappresentazione.
rappresentazione binaria Utilizzando 8 bit lintervallo di rappresentazione
pura su 8 bit. linsieme dei numeri interi postivi compresi in [0,28-
1], ovvero in [0,255]. Poich 16 racchiuso in tale
intervallo possibile passare alla sua codifica.
Utilizzando lalgoritmo dellesempio (1) si ha:
16:2 -> q1=8, r1=0
8:2 -> q2=4, r2=0
4:2 -> q3=4, r3=0
2:2 -> q4=1, r4=0
1:2 -> q5=0, r5=1
Considerando la successione dei resti invertita
[r5,r4,,r1] e inserendo gli zeri a sinistra del bit pi
siginificativo, si ha che il numero in binario
corrispondente : 00010000.
Si vuole convertire il Poich la rappresentazione per complementi a 2
numero -12 in binario consente la codifica di numeri relativi allora -12
utilizzando una codificabile attraverso questa rappresentazione.
rappresentazione per Utilizzando 5 bit lintervallo di rappresentazione
complementi a 2 su 5 bit linsieme dei numeri interi compresi in [-25-1,25-1-1],
ovvero in [-16,15]. Poich -12 racchiuso in tale
intervallo possibile passare alla sua codifica.
Poich -12 negativo per ottenere il corrispettivo in
binario si codifica il suo modulo su 5 bit, si
complimentano le cifre ottenute e si aggiunge 1.
Utilizzando lalgoritmo dellesempio (1) si ha:
12:2 -> q1=6, r1=0
6:2 -> q2=3, r2=0
3:2 -> q3=1, r3=1
1:2 -> q4=0, r4=1
Considerando la successione dei resti invertita
[r4,,r1] e inserendo gli zeri a sinistra del bit pi
siginificativo, si ha che il numero in binario
corrispondente 01100.
Complementando le cifre ottenute si ha 10011, infine
aggiungendo 1 al numero complementato si ha:
10100
Si vuole convertire il Poich la rappresentazione per complementi a 2
numero -10 in binario consente la codifica di numeri relativi allora -10
utilizzando una codificabile attraverso questa rappresentazione..
rappresentazione per Utilizzando 4 bit lintervallo di rappresentazione
complementi a 2 su 4 bit. linsieme dei numeri interi compresi in [-24-1,24-1-1],
26 Capitolo primo

ovvero in [-8,7]. Poich -10 non racchiuso in tale


intervallo la sua codifica non possibile.
Si vuole convertire il Innanzitutto si esamina il primo bit. Poich esso
numero 111000 da pari ad 1, il numero negativo e quindi, per
complementi a 2 in determinarne il modulo, si complementano ad 1 tutti i
decimale. bit e si somma 1 al risultato. Il complemento
000111, mentre sommando a tale numero 1, si ottiene
001000, il cui corrispettivo in decimale 0*25 + 0*24
+ 1*23+ 0*22 + 0*22 + 0*20 = 8.
Il numero decimale corrispondente quindi -8.
Lo stesso risultato poteva essere ottenuto, in via
alternativa, aggiungendo a -2l-1, ovvero a -25 = -32, la
codifica in decimale di 11000, ovvero 1*24 + 1*23+
0*22 + 0*22 + 0*20 = 24.
Infatti -32+24=-8.
Si vuole codificare il Innanzitutto si codificano in binario la parte intera e
numero reale 8.25 frazionaria della mantissa e lesponente (utilizzando
utilizzando la la rappresentazione per eccessi a 2l-1) ottenendo 0
rappresentazione in 111.01 * 200000000.
virgola mobile nel formato Normalizzando la mantissa, si ottiene:
a singola precisione. 0 1.1101 * 210000010

Esempio 7 Alcuni esempi di conversione

1.3. Gli operatori booleani


Sulle stringhe di bit sono anche definiti operatori che lavorano bit a bit (bitwise
operator). Essi sono detti booleani e sono:
- AND: dati due bit restituisce il valore 1 se e solo se i bit erano entrambi
posti a 1, in tutti gli altri casi il risultato 0; lAND detto anche prodotto
logico.
- OR: dati due bit restituisce il valore 0 se e solo se i bit erano entrambi
posti a 0, in tutti gli altri casi il risultato 1; lOR anche detto somma
logica.
- NOT: dato un bit restituisce il valore 0 se esso era posto a 1, restituisce
invece 1 se il bit era posto a 0; il NOT viene anche detto operatore di
negazione o di complementazione.
La figura che segue riporta la definizione completa dei tre operatori: la tabella
viene anche detta tavola di verit.
Linformazione e le sue rappresentazioni 27

Tabella 16 AND, OR, NOT


Si noti che la somma logica di 1 OR 1 fa 1 e non 0 con riporto 1 come nel
caso della somma aritmetica. Di seguito si riportano alcuni esempi di applicazione
dei tre operatori a dei byte.

Tabella 17 Esempi di AND, OR, NOT

1.4. La convergenza digitale


Negli ultimi anni tantissimi settori produttivi hanno migrato i loro sistemi realizzati
con tecnologie, anche molto diverse tra loro, in sistemi capaci di trattare
linformazione in modo digitale.
Lo sviluppo della tecnologia dei sistemi di comunicazione e di gestione delle
informazioni digitali nei campi delleditoria, della televisione, del cinema ha
comportato non solo benefici economici notevoli, ma soprattutto la capacit di
integrare e di elaborare informazioni provenienti da fonti diverse. E lintegrazione
digitale tanto pi significativa quanto pi consente di far condividere a soggetti
differenti e distanti il proprio patrimonio informativo.
Il fenomeno, detto convergenza digitale, si caratterizza per la concomitanza di
diversi fattori:
28 Capitolo primo

- la convergenza della codifica in quanto linformazione in una sua


qualsiasi forma viene rappresentata con un solo alfabeto di base: il codice
binario;
- la convergenza tecnologica perch uno stesso strumento, il calcolatore
elettronico, che elabora e trasmette informazioni differenti;
- la convergenza del mercato che vede applicazioni diverse dotate di
elementi comuni attraverso cui integrarsi.
Oggi viviamo in un mondo caratterizzato da una straordinaria ricchezza di
sorgenti di informazione (suoni, voci, rumori, musiche, immagini, fotografie,
filmati) e differenti canali di comunicazione (radio, televisione, satellite, rete
Internet, CD, DVD) e ritroviamo nel mondo digitale la stessa variet di forme
comunicative.
La trasformazione della realt analogica in forma digitale offre quindi
immense e nuove opportunit: la pi evidente che ogni informazione pu essere
scambiata in processi di comunicazione. Secondo Marshall McLuhan, che negli
anni 60 diede forza agli studi sulla teoria della comunicazione, i concetti
emergenti che nascono dallanalisi delle tecnologie digitali sono la globalit della
comunicazione (il cosiddetto villaggio globale) e i medium. Sempre secondo
McLuhan:
- La nuova interdipendenza elettronica ricrea il mondo come immagine di
un villaggio globale
- I media sono estensioni delluomo: tecnologie e prodotti che danno ai
nostri sensi nuove possibilit di ricevere informazioni
Il termine medium deriva dal latino e significa mezzo: va pertanto inteso come
mezzo di comunicazione. Con multimedialit si indica invece la combinazione di
diversi codici espressivi (testo, audio, immagini, video) per realizzare un unico
oggetto comunicativo che, grazie alle tecnologie digitali, si rappresenta con il
medesimo linguaggio basato su stringhe di bit.
La forza della multimedialit risiede nel convincimento che lintegrazione di
diverse forme espressive migliori la comunicazione. Ma lo studio della
comunicazione non pu prescindere dallo studio delle tecnologie dei mezzi di
comunicazione in quanto questi ultimi non sono neutri, ossia possono:
- condizionare le modalit di rappresentazione delle informazioni;
- alterare la percezione del messaggio,
- spezzare la simmetria di comunicazione coinvolgendo diversi destinatari
passivi (ad esempio radio, televisione),
- alterare le relazioni temporali tra gli interlocutori (ad esempio i messaggi
registrati).

La trasformazione di testo, audio, immagini, video in oggetti digitali pu


avvenire in modo diretto o attraverso dispositivi capaci di effettuare la conversione
che vengono detti ADC (Analogue to Digital converter). Nel primo caso esistono
applicazioni informatiche (elaborazione di testi) o macchine elettroniche (macchine
fotografiche o cineprese digitali) che forniscono direttamente le informazioni in
binario. I dispositivi del secondo tipo, quali ad esempio gli scanner, servono al
recupero in digitale di informazioni rappresentate nei loro formati tradizionali.
In entrambi i casi la trasformazione dellinformazione analogica in digitale
avviene con due processi distinti e disposti luno dopo laltro:
Linformazione e le sue rappresentazioni 29

- il campionamento e
- la quantizzazione.
Il campionamento il processo che provvede a selezionare il sottoinsieme
discreto di informazioni da rappresentare in digitale da quello pi ampio offerto
dalla realt. il partizionamento di un flusso continuo di informazione in quantit
discrete, rispetto al tempo, allo spazio o ad entrambi. La scelta dei campioni viene
effettuata in modo che linformazione rappresentata sia utilizzabile. Ad esempio
non ha molta importanza nel caso delle comunicazioni telefoniche, selezionare un
numero di campioni molto elevato nellunit di tempo della voce, perch lorecchio
umano non coglierebbe affatto le differenze.

Figura 9 Campionamento di un segnale con periodi differenti

Nel caso di grandezze dipendenti dal tempo la frequenza di campionamento


il numero di campioni prelevati in un secondo (unit di tempo). La frequenza si
misura in hertz (1 campione in un secondo) ed linverso dellintervallo di tempo
che intercorre tra lacquisizione di un campione e il suo successivo, che viene detto
periodo di campionamento.
La quantizzazione invece il processo che misura le caratteristiche (ad
esempio grandezza, intensit, colore) dei campioni selezionati attribuendo loro un
valore numerico. Di norma, i campioni selezionati vengono codificati in binario
30 Capitolo primo

sulla base dellappartenenza a dati intervalli di valori, detti intervalli di


quantizzazione.

Figura 10 Quantizzazione di un segnale

Sia campionamento che quantizzazione introducono unapprossimazione della


realt: il primo processo seleziona un sottoinsieme discreto di un insieme di valori
solitamente infinito, il secondo misura le caratteristiche scelte con un numero di
cifre prefissato.
Numero di campioni e numero di bit determinano anche la dimensione della
rappresentazione della grandezza reale: ad esempio nella figura la grandezza G
necessita in un caso di 88 bit, nellaltro di soli 66 bit. In tutti i processi di
digitalizzazione si deve pertanto raggiungere un compromesso tra qualit e
dimensione della rappresentazione: pi sono i campioni e i bit utilizzati, maggiore
la fedelt della rappresentazione e quindi anche la sua qualit. Ma quanto pi si
privilegia la qualit della rappresentazione, tanto pi si rallentano le elaborazioni e
lo scambio delle informazioni rappresentate.
Per risolvere i problemi connessi con le dimensioni elevate sono stati
introdotti processi di compressione che riducono lo spazio occupato mediante o la
diminuzione del numero di bit necessari per codificare una singola informazione
(compressione entropica) o la diminuzione del numero di informazioni da
memorizzare o trasmettere (compressione differenziale, compressione semantica).
La compressione pu conservare integralmente o no il contenuto della
rappresentazione originale secondo due tecniche principali:
- la compressione senza perdita di informazione (lossless, reversibile) che
sfrutta le ridondanze nella codifica del dato;
Linformazione e le sue rappresentazioni 31

- la compressione con perdita di informazione (lossy, irreversibile) che


invece sfrutta le ridondanze nella percezione dellinformazione.
La compressione lossless avviene tramite una classe di algoritmi che
consentono di ricostruire tutta linformazione iniziale partendo da quella
compressa. Non sempre si ottengono riduzioni significative. I metodi lossy
comportano riduzioni notevoli delle dimensioni, ma la ricostruzione
dellinformazione da quella compressa non per identica a quella iniziale. Tali
metodi rimuovono parti che possono non essere percepite come avviene nel caso di
immagini, video e suoni. Ad esempio gli algoritmi di compressione usati nei
formati GIF e JPEG per immagini fisse sfruttano la caratteristica dellocchio
umano di essere poco sensibile a lievi cambiamenti di colore in punti contigui, e
quindi eliminano questi lievi cambiamenti appiattendo il colore dellimmagine.
Tra le tecniche di compressione lossless si ricordano:
- la Run-length encoding (RLE) che codifica sequenze di valori uguali
premettendo un indicatore di ripetizioni al valore codificato;
- la codifica di Huffman che assegna un numero inferiore di bit alle
sequenze pi probabili attraverso un vettore di codifica;
- la compressione Lempel-Ziv-Welch (LZW) che costruisce dinamicamente
una tabella di codifica con numero variabile di bit sulla base delle
sequenze incontrate;
- la codifica differenziale in cui ogni dato rappresentato come differenza
rispetto al dato precedente.
- Tra le tecniche di compressione lossy si ricordano:
- la compressione JPEG per le immagini che applica una trasformata nel
dominio delle frequenze (Discrete Cosine Transform) che permette di
sopprimere dettagli irrilevanti riducendo il numero di bit necessari per la
codifica;
- la compressione MPEG per i video che codifica parte dei frame come
differenze rispetto ai valori previsti in base ad una interpolazione;
- la compressione MP3 per laudio che si basa alle propriet psicoacustiche
delludito umano per sopprimere le informazioni inutili.
1.4.1. La codifica delle informazioni testuali
Il testo uno degli oggetti digitali pi diffuso nel mondo informatico. Molte sono
le applicazioni che generano e manipolano i cosiddetti documenti elettronici. Un
testo digitale una stringa di simboli ad ognuno dei quali viene associato un codice
binario secondo un prefissato standard.

Figura 11 - Esempio di codifica di un testo

Alla fine degli anni sessanta l'ente americano di standardizzazione ANSI


(American National Standards Institute) decise di fissare un alfabeto che
32 Capitolo primo

consentisse a tutti i calcolatori, anche di produttori diversi, di poter comunicare tra


loro o con i dispositivi ad essi collegati. I simboli dellalfabeto vennero elencati in
una tabella per codificare, attraverso la posizione assunta da loro in essa, vari tipi
di caratteri: alfabetici, numerici, di punteggiatura, simbolici, e anche alcuni codici
da usare come controllo della comunicazione tra una macchina e l'altra (per
esempio, per segnalare linizio o la fine di una trasmissione). Il trasferimento di un
insieme di informazioni da un calcolatore all'altro su una rete poteva cos essere
effettuato attraverso un linguaggio comune, costituito da tale forma di codifica.
La tabella fu chiamata ASCII, ossia American Standard Code for Information
Interchange. Le prime 32 posizioni (da 0 a 31) sono occupate da codici di
controllo. La posizione o codice 32 si riferisce allo spazio, dopo di esso seguono
tutti caratteri visualizzabili. Per esempio, l'alfabeto (inglese) maiuscolo occupa le
posizioni da 65 a 90, e quello minuscolo le posizioni da 97 a 122. Le cifre
occupano le posizioni da 48 a 57. In totale ci sono 128 codici, da 0 a 127, e quindi
sono sufficienti 7 bit per la codifica della tabella ASCII.
La tabella stata definita per rendere comoda l'elaborazione dei caratteri: per
esempio, i codici di controllo hanno i due bit a sinistra uguali a zero, e quindi basta
guardare tali due bit per capire se un carattere di controllo o stampabile. Un'altra
caratteristica quella di aver disposto le cifre in sequenza, da 0 a 9, cos che, se si
sottrae il codice di '0' (che 48) dal codice di qualunque altra cifra, si ottiene il
valore numerico di tale cifra. Anche l'alfabeto elencato in successione, cos che
sottraendo dal codice di qualunque lettera il codice della prima lettera dell'alfabeto
(cio 'A' per le maiuscole, codice 65, oppure 'a' per le minuscole, codice 97) si
ottiene la posizione della lettera nell'alfabeto. Inoltre la posizione relativa delle
lettere maiuscole e minuscole fissa, e per la precisione ogni minuscola dista dalla
corrispondente maiuscola esattamente 32 posizioni. Quindi passare da maiuscolo a
minuscolo molto semplice: basta sommare 32 al codice, e sottrarre 32 per la
conversione inversa.
Un testo cos rappresentato dalla sequenza di byte associati ai caratteri che
lo compongono, nell'ordine in cui essi compaiono. Un testo di 1000 caratteri
richiede 1000 byte (1 Kb) per essere rappresentato.
La proposta iniziale dello standard ASCII ha come limite quello di essere
stato pensato per la sola lingua inglese americana in quanto mancano tutte le lettere
accentate in uso nei paesi europei. Per superare la localizzazione, cio la
dipendenza da una determinata area geografica e culturale, sono state proposte
varianti della tabella ASCII.
Lo standard ISO 8859 definisce un ASCII che usa 8 bit per estendere la
tabella con ulteriori 128 caratteri. Le tabelle ASCII estese, quindi coincidono con il
codice americano nei primi 127 caratteri. Ogni nazionalit, poi, pu localizzare
l'insieme dei caratteri disponibili aggiungendo quelli necessari alle proprie
esigenze nel nuovo spazio disponibile, quello dei codici da 128 a 255. Esistono
cos diverse varianti dell'ISO-8859: la versione ISO-8859-1 usata in Europa ed
detta anche ISO-latin-1, la sua configurazione col simbolo dell'euro la ISO-8859-
15.
Linformazione e le sue rappresentazioni 33

Tabella 18 Codice ASCII esteso

Con la diffusione di Internet a livello mondiale, anche i codici ASCII estesi


hanno per mostrato la loro inadeguatezza. Basta pensare alle lingue che usano
ideogrammi anzich alfabeti per rendersi conto che un insieme di soli 127 simboli
largamente insufficiente. Uno standard che si propone di affrontare il problema
del multilinguismo Unicode (Universal Encoding). Unicode un sistema di
codifica che assegna un numero univoco ad ogni simbolo in maniera indipendente
dal programma, dalla piattaforma e dalla lingua (e relativo alfabeto): il suo scopo
quello di creare una codifica delle scritture a livello universale. Unicode si basa
sulla codifica ASCII, ma va oltre la limitazione dell'alfabeto latino potendo
codificare caratteri scritti in tutte le lingue del mondo. Originariamente si basava su
una codifica a 16 bit che dava la possibilit di codificare pi di 65 mila caratteri.
Per rappresentare qualunque carattere, compresi quelli cinesi e tutte le loro
varianti, stato proposto lo standard Unicode (ISO-10646) che utilizza l'UTF
(Unicode Transformation Format) che supporta tre forme di codifica per
rappresentare un repertorio comune di caratteri che pu essere esteso fino a
rappresentarne circa un milione.
34 Capitolo primo

I formati UTF possono essere a 8, 16 e 32 bit. L'UTF-8 si basa su parole di 8


bit (1 byte) per la codifica dei caratteri; ed usa da 1 a 4 byte per carattere: i primi
128 valori, che iniziano col bit 0, utilizzano 1 byte per carattere e corrispondono
all'ASCII, i successivi 1920 (dal 128 al 2047) utilizzano 2 bytes per codificare
greco, cirillico, copto, armeno, ebraico, arabo. Per gli altri caratteri si usano 3 o 4
bytes. UTF-16 utilizza parole di 16 bit per codificare i caratteri, viene utilizzato da
Java, ed la rappresentazione interna usata da Windows e Mac OS-X.
Una interessante conseguenza della gestione dei testi digitali la possibilit di
costruirne una strutturazione diversa da quella sequenziale dei documenti, alla
quale i libri hanno abituato i lettori. La suddetta possibilit ha portato alla nascita
dei cosiddetti ipertesti. Un ipertesto un documento strutturato in cui un insieme di
frammenti di testo detti nodi vengono collegati per mezzo di riferimenti detti link.
In un ipertesto la fruizione del documento consiste nel muoversi da un nodo
allaltro seguendo i collegamenti. Lidea formalizzata di ipertesto nasce nella met
del secolo scorso, la maturazione tecnologica avviene solo negli anni 80.
Lapproccio ipertestuale levoluzione di un approccio non sequenziale alla lettura
dei documenti gi utilizzato nella realizzazione di quotidiani, enciclopedie, libri
con annotazioni e riferimenti, manuali, cataloghi. La digitalizzazione dei testi ne ha
consentito una pervasiva applicazione come testimoniato dal mondo di Internet.
Il testo digitale ha, tra i tanti vantaggi, quello di poter essere facilmente
trasmesso in reti di calcolatori per essere mostrato sugli schermi o stampato su
fogli di carta. Per garantire la presentazione elettronica o cartacea vengono
aggiunte informazioni che fissano caratteristiche di formato quali il tipo e la
dimensione del carattere, lo spazio tra una riga e laltra, il formato della pagina. La
tecnica pi utilizzata per la codifica consiste nell'inserire, assieme al testo,
informazioni speciali di formattazione che vengono interpretate in fase di
presentazione finale.
Il linguaggio HTML l'esempio pi diffuso per la codifica di testo formattato
finalizzato ad una consultazione in rete. Il codice HTML testuale e intuitivo.
Infatti le specifiche di formato vengono racchiuse tra parentesi che prendono il
nome di TAG. I TAG si distinguono dal resto del testo perch sono inclusi tra
parentesi angolate: con <nometag> si apre una indicazione di formato e con
</nometag> la si chiude. Ad esempio:

<font color=red> che bello </font>

prescrive la visualizzazione di che bello in rosso, mentre:

<font size=+1>HTML</font>

consente di scrivere HTML con caratteri di dimensioni pi grandi.


Il DOC invece il formato pi diffuso dei documenti formattati a causa
dellampia diffusione delleditor che lo gestisce (MicroSoft Word). Il Postscript
un altro linguaggio per la codifica di testo formattato finalizzato ad una
presentazione cartacea. Una sua evoluzione il PDF, sviluppato nel 1992 da
Adobe Systems, che stato pensato sia per la riproduzione cartacea che elettronica.
Con tale formato i documenti consultati a video hanno lo stesso identico aspetto
del documento stampato.
Linformazione e le sue rappresentazioni 35

1.4.2. La codifica delle immagini


Le immagini sono informazioni continue in tre dimensioni, due spaziali ed
una colorimetrica, e per codificarle occorre operare tre discretizzazioni. Le due
discretizzazioni spaziali riducono limmagine ad una matrice di punti detti pixel
(da picture element) mentre la terza limita linsieme di colori che ogni pixel pu
assumere ad un sottoinsieme definito.
Limmagine digitale quindi una matrice bidimensionale di numeri, ciascuno
dei quali la misura di una propriet fisica (colore) di unarea elementare della
scena rappresentata. Una immagine digitale pu essere generata:
- mediante acquisizione da immagini analogiche (ad esempio le fotografie,
le diapositive) con dispositivi detti scanner;
- da scene del mondo reale catturate con camere digitali;
- da applicazioni di grafica.
Le immagini digitali riproducono la scena dividendola in una griglia fatta di
aree di cui viene misurata la luminosit o il colore. Ad ogni area viene fatto
corrispondere un pixel la cui forma si discosta dalla superficie ripresa. Infatti per le
caratteritiche della maggioranza dei dispositivi elettronici di acquisizione e
visualizzazione delle immagini i pixel hanno la forma di un ellisse con lasse
verticale pi lungo rispetto a quello orizzontale. Il rapporto tra i due assi viene
detto rapporto di aspetto e serve, nelle applicazioni grafiche, per correggere
eventuali deformazioni dovute a rappresentazioni diverse di uno stesso segmento
nelle due direzioni ortogonali.

Figura 12 - Immagine come griglia di pixel

Il processo di campionamento applicato ad una immagine consiste quindi nel


far corrispondere ad ogni pixel una porzione dellimmagine reale. Pi la
dimensione dei pixel piccola, minore lapprossimazione tra immagine digitale e
quella reale. Con il termine di risoluzione si indica il numero di pixel per pollice
(dpi - dot per inch) perch le dimensioni di unimmagine (larghezza e altezza) sono
misurate in pollici. La dimensione dellimmagine viene anche espressa indicando
separatamente il numero di pixel orizzontali e il numero di pixel verticali, ad
esempio 600 x 800 pixel. Solitamente la risoluzione orizzontale uguale a quella
verticale. Ad ogni pixel viene poi assegnato un indirizzo che ne determina le
coordinate verticali ed orizzontali (bit mapping).
Il concetto di risoluzione legato a quanto sono fitti i punti che visualizzano
limmagine. Maggiore la risoluzione dellimmagine, maggiore la possibilit di
36 Capitolo primo

distinguere dettagli in essa presenti. Tutti i pixel contenuti in una immagine


digitale hanno dimensioni identiche. La loro dimensione determinata dalla
risoluzione alla quale limmagine viene digitalizzata: ad esempio la risoluzione di
600 dpi indica che ciascun pixel misura 1/600 di pollice.
Ad ogni pixel dellimmagine vengono associati l bit che misurano
caratteristiche di colore. Ma con l bit solo 2l sono le sfumature di colore
rappresentate degli infiniti valori della realt, e tutte le sfumature intermedie sono
approssimate con il valore di luminosit pi prossimo fra quelli codificati.
Poich la porzione di immagine associata ad un unico pixel ha una luminosit
uniforme, senza che dettagli in detta porzione possano essere distinti, minore il
numero dei livelli di quantizzazione, minore la qualit dellimmagine. La
profondit di colore la misura della capacit di rappresentare o distinguere varie
sfumature di colore. Unimmagine rappresentata con una profondit di colore di 6
bit, riesce a distinguere tra 64 livelli di colore, e allaumentare del numero di bit,
aumenta il livello di dettaglio. Se limmagine in bianco e nero, basta associare un
1 ai pixel neri, e uno 0 a quelli bianchi. Per immagini a livelli di grigio si usano 4 o
8 bit mentre per quelle a colori 8, 24, 32 bit.

Figura 13 - Immagini a diverse profondit di colore

Si parla di colori veri quando ad un pixel corrispondono 24 bit per un totale di


16.7Mega colori diversi; con 48 bit viene oggi gestita lalta definizione.
La rappresentazione accurata di una immagine dipende dal numero di pixel e
dalla profondit di colore. Una elevata qualit comporta una elevata quantit di
informazione data proprio dal prodotto:
numero pixel numero bit
Per fare alcuni semplici esempi si passa dai 440KByte di una immagine
televisiva con 256 colori (1 byte) per una risoluzione di 720x625 pixel, ai
440MByte di una foto con 16 milioni di colori (3 byte) per la risoluzione di ben
15.000x10.000 pixel.
La misura del colore oggetto di studio della colorimetria. Due sono i metodi
usati per formare il colore: la sintesi del colore di tipo additiva e quella di tipo
sottrattiva.
Linformazione e le sue rappresentazioni 37

Figura 14 - Sintesi del colore

Nel primo caso un colore pu essere ottenuto attraverso la miscelazione di


gradazioni dei tre colori primari: il rosso, il verde e il blu. Per i colori su cui si basa
viene detto modello RGB (da red, green, blue). Unendo il rosso e il verde si
ottengono i colori dal giallo allarancio, unendo il rosso e il blu si ottengono dal
porpora al viola. Se i colori primari sono sommati alla loro massima potenza
producono il bianco per questo motivo il modello di tipo additivo.
Il secondo modello detto invece CMY perch usa i colori ciano (Cyan),
magenta, giallo (Yellow). Esso si usa soprattutto nei processi di stampa su carta
perch si basa sulla capacit propria dellinchiostro di assorbire la luce. Quando la
luce bianca colpisce gli inchiostri, alcune lunghezze donda visibili vengono
assorbite, mentre altre vengono riflesse e quindi viste: per questo motivo il modello
di formazione del colore si dice sottrattivo. Anche se il nero pu essere derivato
direttamente dalla combinazione di ciano, magenta e giallo (ossia assorbendo tutti
e tre i colori base), nelle stampanti, per motivi pratici, si una anche linchiostro
nero (black) e il modello prende il nome di CMYK.
In entrambi i casi i bit associati ai pixel di una immagine esprimono la misura
de tre colori base. Nel caso di una profondit di 24 bit e codifica RGB, vengono
assegnati 8 bit per la percentuale di rosso, 8 bit per quella di verde e 8 per quella di
blu. Poich si utilizzano 3 byte per rappresentare ogni pixel, una immagine a colori
di 100x100 pixel avr bisogno di 100 x 100 x 3 byte = 30.000 byte per essere
rappresentata.
Per ridurre la dimensione della rappresentazione si pu utilizzare un sistema
di codifica dei colori mediante tavolozza di colori (detta anche palette o CLUT da
Color Look-Up Table). La palette una codifica dei colori, solitamente con
profondit di 8 bit per pixel, che consente di scegliere 256 colori diversi tra i
milioni di colori esistenti. Si basa sullipotesi che difficilmente in una immagine
sono presenti contemporaneamente 16 milioni di colori diversi. Consiste
nellutilizzare una tabella numerica in cui sono codificati solo i colori
effettivamente presenti nellimmagine: ciascun pixel sar codificato con un numero
limitato di bit (da 4 a 8) che identifica la posizione in tabella del colore da usare. I
colori della palette cambiano a seconda dellimmagine e dipendono dal suo
contenuto: la tavolozza contiene infatti solo il sottoinsieme dei colori
rappresentabili che compare nella foto. Possono essere anche modificati in
38 Capitolo primo

funzione dellutilizzo dellimmagine (stampa o visualizzazione). Se ad una


immagine si cambia la CLUT, cambiano ovviamente le sue sfumature di colore. In
tale codifica ogni immagine viene accompagnata dalla sua palette.

Figura 15 - Codifica mediante palette

Il formato di rappresentazione di immagini per punti detto bitmap o raster.


Esso usato per riprodurre fotografie, dipinti e tutte le immagini per le quali non
ha importanza lindividuazione degli elementi riprodotti. Nelle immagini bitmap
quindi il pixel con le sue caratteristiche lelemento di riferimento.
Il singolo pixel da solo detiene un contenuto informativo limitato, se viene
invece considerato in combinazione allinsieme di pixel adiacenti si possono
estrarre caratteristiche pi significative. Elaborare unimmagine significa
modificarne il contenuto allo scopo di evidenziare alcune caratteristiche.
Analizzare unimmagine significa invece studiarne il contenuto allo scopo di
inferire informazioni relative alla scena rappresentata.
I settori della Computer Vision e della Computer Graphics studiano algoritmi
che cercano di ricostruire dai tanti pixel di una immagine raster sia struttura che
significato degli oggetti presenti in una scena. Ad esempio gli algoritmi OCR
(optical character regognition) restituiscono il testo in formato digitale estraendolo
dalla fotografia raster di un foglio dattiloscritto acquisita tramite scanner.
I formati di rappresentazione pi diffusi sono:
Linformazione e le sue rappresentazioni 39

- il TIFF (Tagged Image File Format) che permette di gestire le immagini


in varie modalit: bianco e nero, scala di grigio, colori RGB, colori
CMYK. Il TIFF prevede un sistema di compressione chiamato LZW
(Lempel-Ziv-Welch) non distruttiva, che non elimina alcuna informazione
n degrada la qualit dellimmagine e che produce buone riduzioni della
quantit di bit della rappresentazione;
- il formato EPS (Encapsulated PostScript File) impiegato inizialmente per
i disegni vettoriali si poi diffuso come standard anche per le immagini
raster e deve la sua importanza al linguaggio PostScript;
- il JPEG (Joint Photographic Expert Group) nato con lo scopo di
standardizzare diversi formati per immagini con compressione di qualit.
La sua principale caratteristica quella di poter far scegliere il livello di
compressione e di modulare quindi il rapporto tra la qualit dellimmagine
e la quantit di bit; la compressione con perdita e si possono raggiungere
livelli di compressione alti (fino a 20:1 contro il 4:1 del GIF);
- il GIF (Graphics Interchange Format) trova largo uso in Internet per la
rappresentazione di elementi grafici come pulsanti, scritte, logo. Permette
inoltre di rendere lo sfondo degli oggetti trasparente per integrarli nelle
pagine del web. un formato con poca quantit di bit, in quanto riduce a
256 la gamma dei colori, utilizzando una codifica che si basa sulluso di
una palette;
- il PNG (Portable Network Graphics) stato inventato per sostituirsi a
GIF nella trasmissione di immagini sulla rete. Gestisce la trasparenza
dello sfondo. Non supporta lanimazione. Presenta un algoritmo di
compressione lossless migliore di quello del formato GIF;
- il BMP (Bitmap) che stato sviluppato per essere compatibile con tutte le
applicazioni del mondo Windows per immagini in b/n, in scala di grigi, in
scala di colore (RGB ma non in CMYK). Non prevede lapplicazione di
metodi di compressione per cui la quantit di bit resta consistente.
Quando le immagini hanno caratteristiche geometriche ben definite, come nel
disegno tecnico, possibile adottare una tecnica pi efficiente per codificare le
figure. Nel caso di progettazione architettonica, meccanica o elettronica, (CAD da
Computer Aided Design) il disegno da memorizzare pu essere facilmente
scomposto in elementi base come una linea o un arco di circonferenza e non
conveniente rappresentare limmagine in termini di pixel ma si procede
descrivendo gli elementi che compongono la figura in termini matematici.
Gli oggetti geometrici che compongono il disegno, quali punti, rette, linee,
curve, cerchi, ellissi, rettangoli, vengono rappresentati secondo formule
matematiche e parametri che li descrivono: ad esempio un punto tramite le
coordinate, la retta tramite la sua equazione, il rettangolo mediante le coordinate
dei quattro vertici; la circonferenza tramite le coordinate del vertice e la
dimensione del raggio. Tale tipo di rappresentazione si definisce vettoriale. In essa
presente tutta linformazione necessaria a riprodurre limmagine, a prescindere
dalle dimensioni del dispositivo di visualizzazione. Un grande vantaggio delle
immagini vettoriali rispetto a quelle raster risiede nella minor quantit di bit usata
per la loro rappresentazione e nella capacit di essere variate di dimensioni senza
subire alcuna distorsione.
40 Capitolo primo

Le immagini bitmap, invece, se ridimensionate rispetto alle dimensioni


originali di acquisizione, hanno la tendenza a perdere di risoluzione risultando
distorte o sfocate. Un ulteriore vantaggio della rappresentazione vettoriale risiede
nel fatto che tutti gli oggetti che compaiono nella figura mantengono la loro
identit in termini di caratteristiche descrittive per cui sono facilitate le operazioni
di modifica dellimmagine come solitamente accade nei settori della progettazione.
Ovviamente non adatta per rappresentare immagini composte da continue
variazioni di colore, quali ad esempio le fotografie.

Figura 16 Immagine vettoriale e raster

1.4.3. Immagini in movimento o video


Il problema della rappresentazione delle immagini in movimento (o video)
viene risolto allo stesso modo in cui il cinema o la televisione lo hanno affrontato:
sfruttando un limite della capacit percettiva dell'occhio umano. La retina
dellocchio umano ha la caratteristica di mantenere impressa unimmagine per
alcuni millisecondi prima che svanisca. Se si proietta una successione di immagini
a determinate velocit, locchio non si accorge che ci che vede una sequenza
discreta e percepisce il movimento come un continuo.

Figura 17 Video come sequenza di immagini

Un video una sequenza di immagini disposte in successione temporale, ossia


una dopo laltra. La sequenza continua di immagini della realt viene quindi
discretizzata ottenendo una serie di immagini (detti frame) che variano
velocemente, ma a intervalli stabiliti. Il frame-rate il numero di frame mostrati
per secondo (fps). Al cinema il frame-rate di 24 fps. Il sistema televisivo europeo
(PAL) ha un frame-rate di 25 fps mentre quello americano (NTSC) ne prevede 30
fps.
Linformazione e le sue rappresentazioni 41

Per digitalizzare limmagine in movimento necessario digitalizzare ogni


singolo frame con la tecnica vista per le bit-map. In questo modo la quantit di bit
usati per la rappresentazione dipende dalla risoluzione di ogni singola immagine,
dalla sua profondit di colore e dalla durata del video che fissa il numero di frame
complessivi. Ad esempio un minuto di trasmissione video con frame di 320x240
pixel e 256 colori richiede:
320 240 (pixel per frame) 25 (frame per sec) 1 (byte per pixel) 60 (secondi)
= 115 Mbyte/min
Un CD non conterebbe pi di 5 minuti di video. quindi necessario applicare
tecniche di compressione.
I CODEC (CODifica e la DECodifica) sono gli algoritmi (o le applicazioni
che li realizzano) utilizzati per comprimere ed espandere i video digitali in modo
da renderne pi efficiente la gestione e la trasmissione.
Lo standard MPEG (Moving Picture Experts Group), associa alla semplice
codifica di ciascuna immagine anche tecniche per il suono e, soprattutto, modalit
di compressione che sfruttano il fatto che la differenza tra un frame e il successivo
minima. Invece di conservare le informazioni di ogni frame, vengono conservate
solo quelle essenziali a ricostruire la scena originaria e quelle che si modificano,
tralasciando le variazioni impercettibili. Facendo riferimento allesempio di figura
14 nella codifica MPEG vengono codificati solo il primo frame e le differenze tra
ogni frame ed il successivo (zone delle immagine in cui si muove la pallina). E
cos immediato dato un frame ricostruire il successivo aggiungendo al precedente
le sole differenze.
MPEG-1 fu definito nel 1989 e rilasciato nel 1992. Questo sistema di
compressione del segnale audio-video, ideato per i CD-Rom e per la visione di
piccoli filmati su Internet, consente di vedere filmati televisivi con una qualit
paragonabile a quella di un videoregistratore. Tra il 1992 e il 1994 si afferma
MPEG-2, lo standard pensato per la trasmissione di contenuti multimediali sulla
televisione digitale via satellite e via cavo.
Oggi tutte le televisioni digitali degli Stati Uniti, buona parte di quelle
europee, ed i DVD si basano sullo standard MPEG-2. Nel 1998 viene approvato
MPEG-4 che rappresenta i frame a partire dagli oggetti di cui sono composti che
mantengono una loro individualit sia nella fase di codifica che in quella di
rappresentazione finale. Ad esempio, in un video composto da un paesaggio e da
un motociclista che si muove, non necessario ritrasmettere pi volte le
componenti invarianti del paesaggio, ma sufficiente trasmettere solo quelle che
cambiano legate agli spostamenti della moto e del suo guidatore.
Lorganizzazione ad oggetti l'aspetto innovativo di MPEG-4: qualsiasi
filmato pu essere arricchito di ulteriori oggetti quali immagini fisse, videoclip,
sorgenti audio, che vengono attivate grazie alla presenza di oggetti cliccabili e
navigabili come su Internet. Si interviene quindi sul video rendendolo interattivo.
Nel 2001 viene proposto MPEG-7 che pu essere definito uno standard di
descrizione piuttosto che di compressione. MPEG-7 non sostituisce le versioni
precedenti, ma le affianca consentendo di estrarre informazioni da tutti gli oggetti
audiovisivi esistenti per una indicizzazione e catalogazione sul modello di un
motore di ricerca.
42 Capitolo primo

1.4.4. La codifica del suono


Il suono un segnale analogico funzione del tempo consistente in vibrazioni
che formano unonda, la cui ampiezza misura laltezza dellonda e il periodo la
distanza tra due onde.
Anche il suono deve essere campionato e discretizzato per poter essere
digitalizzato. Se il campionamento troppo rado e vengono usati pochi bit per
misurare ogni valore istantaneo, la qualit del suono degrada nel senso che il suono
riprodotto diverso da quello originale. L'operazione di campionamento
discretizza il segnale con una frequenza dell'ordine delle decine di KHz (migliaia
di campioni al secondo) perch dimostrato che lorecchio umano percepisce
fedelmente il suono originale se il suo campionamento non inferiore a 30KHz.
Particolare circuiti elettronici ricostruiscono il segnale originale e Nyquist ha
dimostrato che per ottenere il segnale iniziale senza perdite necessario
campionare con una elevata frequenza di campionamento, pari ad almeno due volte
la frequenza massima del segnale stesso. La quantizzazione , diversamente da un
buon campionamento, un processo irreversibile che conduce ad una sicura perdita
di informazioni; tanto pi l'operazione accurata tanto pi la qualit del suono
preservata riducendo al minimo quello che viene detto rumore di quantizzazione.
La quantit di bit usati per rappresentare il suono dipende allora dal numero di bit
usato per la quantizzazione, chiamato anche profondit del suono, dalla frequenza
di campionamento e quindi dalla durata del suono.
Ad esempio per una linea telefonica sufficiente una frequenza di
campionamento di soli 8KHz con una quantizzazione a 256 livelli (codificati con 8
bit) per riprodurre la voce umana ai due estremi del collegamento garantendo la
comprensione del parlato. Nel caso di musica stereo la quantit di bit raddoppia
perch vanno separatamente digitalizzati i segnali per il lato destro e per quello
sinistro.

Tipo Frequenza di Profondit Mono/stereo Dimensione


campionamento (bit) per un minuto
(Hz)
Telefono 8.000 8 mono 469,00 Kb
Parlato 11.025 8 mono 646,00 Kb
Radio mono 22.050 16 mono 2,52 Mb
Radio stereo 22.050 16 stereo 5,05 Mb
Audio Cassetta 44.100 16 stereo 10,10 Mb
Compact Disk 48.000 16 stereo 11,00 MB
Tabella 19 Frequenze di campionamento per il suono

Da MPEG-1 ha avuto origine Mp3 (abbreviazione di MPEG-1 layer 3). Mp3


una codifica del segnale audio che consente la compressione di brani musicali della
durata di 3-6 minuti con pochi MB rendendone possibile la distribuzione nella rete
Internet.
Linformazione e le sue rappresentazioni 43

1.5. Dati e metadati


Nella realt di tutti i giorni molte attivit operano con informazioni di natura e
forma diverse (testo, video, audio) e si costruiscono sistemi che gestiscono e
elaborano informazioni.
Linformazione quindi un oggetto che ha un rapporto stretto con la realt
dalla quale pu emergere se e solo se un determinato insieme o classe di oggetti
assume stati o configurazioni differenti. In tale accezione linformazione non
altro che la scelta di una delle possibili configurazioni in cui si trova un esemplare
della classe di oggetti. Allora, il concetto di informazione strettamente legato a
quello di scelta di uno fra pi oggetti di un particolare insieme e non esiste
informazione se non si effettua una scelta. Ad esempio, la frase sto studiando
elementi di informatica, fornisce un'informazione in quanto esprime la scelta della
materia di studio e l'identificazione, quindi, della materia elementi di
informatica, tra tutte le possibili materie del piano di studio.
In informatica stato introdotto il termine dato che deriva dal latino datum e
significa letteralmente fatto. Mentre luomo tratta informazioni lelaboratore tratta
dati. Con dato si indica la rappresentazione di fatti e concetti in modo formale
perch sia possibile una loro elaborazione da parte di strumenti automatici. Il dato
da solo, senza un contesto, pu non avere significato: uno stesso numero pu
esprimere cose diverse in situazioni diverse; cos come una stessa parola pu avere
significato dipendente dal contesto.
Lambiguit pu essere risolta dando al dato una interpretazione. Linformazione
non altro che la percezione del dato attraverso un processo di interpretazione. In altre
parole linformazione cattura il significato del dato. Quando lelaborazione consente di
trattare dati eterogenei in modo integrato si parla di elaborazione multimediale.
Il termine metadato apparso abbastanza recentemente anche se indica
tipologie di informazioni diffuse da molto tempo. Nella vita reale i metadati
vengono impiegati principalmente per organizzare informazioni o cose, e,
ovviamente per effettuare ricerche tra informazioni o cose classificate, come
avviene nei cataloghi delle biblioteche dove ogni libro viene etichettato con una
scheda (riportante titolo, autore, anno di pubblicazione, casa editrice, argomenti
trattati, etc.) di cui si conosce luso.
I metadati indicano dati che descrivono altri dati riportandone struttura,
significato o descrizione. Possono essere distinti nella categorie di metadati
descrittivi finalizzati al recupero dei dati ed in metadati gestionali necessari alla
gestione dei dati. Anche lattributo di un dato un altro esempio di metadato in
quanto descrive il ruolo svolto da una variabile in un contesto applicativo. I
metadati sono solitamente pi facili da trattare dei dati che rappresentano perch
hanno un formato prestabilito mentre il formato dei dati dipende da molti fattori.
Un aspetto interessante dei metadati che anchessi sono dati e come tali
possono essere gestiti nel senso che possono essere descritti da altri metadati, e
cos via. Il numero di metalivelli da specificare dipende dalle caratteristiche delle
applicazioni e dalle specifiche esigenze.
Secondo le nuove proposte del web semantico il metadato linformazione
che da significato al dato rendendolo machine understandable, ossia
comprensibile alle macchine. Esso costituisce lo strumento principale del Web
44 Capitolo primo

Semantico in quanto permettono di introdurre la semantica per descrivere il


contenuto dei documenti web.
LeXensible Markup Language o XML oggi il linguaggio standard su cui si
innestato lo sviluppo del Web Semantico e la sua principale caratteristica consiste
nel fatto che permette di definire delle strutture dati indipendenti da qualsiasi
piattaforma e che possono essere elaborate in modo automatico. XML non un
linguaggio di programmazione ma un linguaggio di marcatura (markup) con una
sintassi semplice per rendere agevole sia la lettura diretta che la elaborazione
automatica
Capitolo secondo

Il modello di esecutore

2.1. Processi e processori


Linformatica ha avviato alla fine del ventesimo secolo una rivoluzione che ha
prodotto effetti analoghi a quelli osservati all'epoca della rivoluzione industriale.
Ma mentre la rivoluzione industriale ha segnato il potenziamento della forza fisica
dell'uomo (amplificazione dei suoi muscoli) la rivoluzione informatica ha portato
ad un aumento della potenza della mente dell'uomo (amplificazione del suo
cervello). Cos come le macchine meccaniche sostituiscono l'uomo in azioni
ripetitive o faticose, le macchine informatiche o computer sostituiscono l'uomo
nelle attivit ripetitive della sua mente.
Un computer un apparecchio elettronico che, strutturalmente, non ha niente
di diverso da un televisore, uno stereo, un telefono cellulare o una calcolatrice
elettronica, semplicemente progettato per eseguire autonomamente attivit
diverse sia nello stesso tempo, che in tempi diversi. Come tutte le macchine, non
ha nessuna capacit decisionale o discrezionale, ma si limita a compiere
determinate azioni secondo procedure prestabilite. Si pu anzi affermare,
paradossalmente, che il computer una macchina che in maniera automatica
esegue operazioni elementari ad altissima velocit. L'altissima velocit di
elaborazione (milioni di istruzioni per secondo) fa s che operazioni complesse
(espresse mediante un gran numero di operazioni semplici) siano eseguite in tempi
ragionevoli per l'ambiente esterno.
Come tutti gli esecutori di ordini anche il computer pu compiere solo quei
lavori che possono essere specificati mediante operazioni che in grado di
comprendere e mettere in pratica. Lalgoritmo la descrizione di un lavoro da
svolgere. Allora se si vuole usare un computer bisogna non solo progettare
preliminarmente un algoritmo, ma anche provvedere a comunicarglielo in modo
che gli risulti comprensibile.
L'esecuzione di un algoritmo da parte di un esecutore si traduce in una
successione di azioni che vengono effettuate nel tempo. Si definisce processo il
lavoro svolto eseguendo l'algoritmo, e processore il suo esecutore. Il processo non
altro che lelenco delle azioni effettivamente svolte come si susseguono nel
tempo. Ogni algoritmo evoca da uno a pi processi, nel senso che, a seconda delle
condizioni in cui il lavoro viene svolto, si possono verificare comportamenti
diversi da parte dellesecutore.
46 Capitolo secondo

Il computer un tipo speciale di processore che evolve in automatico


(funziona senza l'intervento umano), ha un'alta velocit elaborativa (se confrontata
con un esecutore uomo) ed capace di eseguire processi differenti.

2.2. Modello di Von Neumann


Per comprendere i motivi che rendono un computer diverso dalle altre macchine, si
introduce uno schema di riferimento nel quale sono messi in evidenza tre blocchi
fondamentali.

Figura 1 Modello di riferimento di Von Neumann

Lo schema presentato uno schema di principio ed rappresentativo delle


macchine tradizionali. Prende il nome da Von Neumann, il primo ricercatore che
lo propose nel 1945.
La Central Processing Unit (CPU) coordina lesecuzione delle operazioni
fondamentali; la memoria contiene l'algoritmo che descrive le operazioni da
eseguire e i dati su cui l'algoritmo stesso opera; i dispositivi di input e output sono
le interfacce della CPU nei confronti del mondo esterno, rispettivamente sono
lunit che consente l'inserimento di algoritmo e dati in memoria, e quella che
presenta i risultati dell'attivit della CPU.
Queste unit fondamentali formano l'hardware del computer, ossia l'insieme
di tutti i componenti elettronici, elettrici e meccanici che costituiscono un sistema
elaboratore.
Il prototipo proposto da Von Neumann era basato sul concetto di programma
memorizzato: la macchina immagazzinava nella propria memoria i dati su cui
lavorare e le istruzioni per il suo funzionamento. Una tale flessibilit operativa fece
s che macchine nate allo scopo di alleviare i problemi di calcolo per tecnici e
scienziati, potessero essere in seguito impiegate nella risoluzione di problemi di
natura completamente diversa come problemi di tipo amministrativo, gestionale e
produttivo.
Le caratteristiche che un sistema di tale tipo presenta, e che ne hanno
decretato la rapida diffusione in molti campi applicativi, sono:
- uno schema di funzionamento semplice nelle sue linee generali,
Il modello di esecutore 47

- la velocit e l'affidabilit nella esecuzione degli algoritmi;


- una adeguata capacit di memoria;
- un costo vantaggioso.
La velocit di esecuzione si aggira attualmente sui milioni di istruzioni svolte
dalla CPU in un secondo e per tale motivo come unit di misura della capacit
elaborativa dei computer stato usato il MIPS (milioni di istruzioni per secondo).
Vale la pena osservare che, nonostante la velocit dei computer tenda ad
aumentare, esistono problemi che presentano soluzioni informatiche non pratiche
poich il loro tempo di esecuzione resta comunque lungo.
Dal punto di vista dellaffidabilit si pu affermare che un computer non
commette errori e gli errori dovuti a guasti o a cattivi funzionamenti hardware sono
subito riscontrabili, alcune volte persino in maniera automatica. Inoltre un
computer non commette mai errori di algoritmo poich un esecutore obbediente
dell'algoritmo, la cui esecuzione gli stata affidata.
Per memorizzazione delle informazioni si intende il compito della memoria di
conservare informazioni per la CPU. La memorizzazione pu essere temporanea,
permanente o definitiva. Con capacit di memoria si fa riferimento al numero di
informazioni che possono essere gestite. Tale numero varia in base al tipo di
memoria usato, all'architettura della memoria stessa ed al tipo di informazione. La
capacit la misura del numero di informazioni immagazzinabili nella memoria ed
oggi si misura in numero di byte.
Per quanto riguarda il costo dei computer si pu sicuramente considerare che
esso basso se paragonato ai tempi di lavoro necessari affinch esseri umani
portino a termine gli stessi compiti. Ed anche in conseguenza di ci che i
computer vanno sempre pi diffondendosi nei settori produttivi della societ.
Larchitettura di un computer nella realt molto pi complessa. Nelle linee
generali per il funzionamento interno di un qualsiasi computer si pu ricondurre
al semplice schema presentato che non molto dissimile da uno antropomorfo che
vede un essere umano sostituire con il suo cervello la CPU, fogli di carta alla
memoria centrale, una calcolatrice con le operazioni fondamentali all'ALU
(Aritmetic Logic Unit), ovvero il componente della CPU che effettua tutte le
operazioni di calcolo logico e aritmetico.
Per concludere si deve notare che i dispositivi di input e di output
interfacciano la CPU con l'ambiente esterno provvedendo a tutte le trasformazioni
necessarie a rendere comprensibili le informazioni sia alla CPU che agli utenti
esterni del computer. Essi vengono progettati in modo confacente ai meccanismi di
comunicazione delle informazioni dell'ambiente in cui il computer immerso.
Nella maggior parte delle applicazioni pratiche l'uomo l'utente del computer,
ma esistono applicazioni nelle quali il computer scambia informazioni con
macchinari o sonde che rappresentano le informazioni sotto forma di segnali
elettrici. Nelle comunicazioni con un utente umano i dispositivi di input ed output
provvedono alla trasformazione della rappresentazione delle informazioni dal
linguaggio naturale al linguaggio binario e viceversa. In tali casi il tipico organo di
input la tastiera mentre quello di output uno speciale televisore detto monitor o
video. La tastiera fatta pertanto da tasti sui quali sono riportati lettere, cifre e
simboli speciali: mediante la pressione dei tasti si inviano le informazioni in
memoria. Il video o monitor riporta i risultati distribuendoli su un numero di righe
limitate in modo che possano essere letti agevolmente. Si ricordano inoltre il
48 Capitolo secondo

mouse, la penna ottica, la tavoletta grafica e lo scanner come altri esempi di


dispositivi di input; mentre tra quelli di output il plotter e le stampanti ad aghi, a
getto dinchiostro o laser per riportare i risultati su foglio di carta.
2.2.1. Le memorie
In generale le memorie possono essere viste come un insieme di contenitori fisici,
detti anche registri, di dimensioni finite e fissate a cui si pu far riferimento
mediante la posizione occupata nell'insieme detta indirizzo di memoria. La
dimensione di un registro si misura in numero di bit. Il bit un dispositivo capace
di assumere due sole condizioni:
- nelle memorie di tipo elettronico sono circuiti detti flip-flop che mostrano
un valore di tensione o uguale a 5 Volt o a 0 Volt;
- nelle memorie di tipo magnetico una sorta di calamita polarizzata o
positivamente o negativamente;
- nelle memorie di tipo ottico una superficie con o senza un buco in modo
da riflettere diversamente il raggio laser che la colpisce.
In ogni caso il dispositivo di lettura deve essere in grado di associare allo stato
del bit il valore 1 (ad esempio tensione a 5 volt, polo positivo, assenza di buco) o il
valore 0 (tensione a 0 volt, polo negativo, presenza di buco).
Memorie con registri di otto bit sono dette a byte o caratteri; con pi di otto
(solitamente 16 o 32) vengono invece dette a voce. I calcolatori moderni sono
dotati di memorie a byte e le memorie a voce sono solo un ricordo del passato.
Le operazioni consentite su un registro sono di lettura e di scrittura. Con la
prima si preleva l'informazione contenuta nel registro senza per distruggerla; con
la seconda si inserisce una informazione nel registro eliminando quella precedente.
Per comprendere il funzionamento di un registro di memoria si pu pensare ad una
lavagna il cui uso pu essere cos esemplificato:
- leggere informazioni a patto che vi siano state scritte;
- la lettura non cancella quanto scritto;
- la scrittura di nuove informazioni obbliga a cancellare quelle precedenti
che pertanto vengono perse.
La memoria un sistema che assolve al compito di conservare il dato,
depositandolo in un registro nel caso di operazione di scrittura, e di fornire il dato
conservato in un registro, in caso contrario di operazione di lettura. Le due
operazioni vengono anche dette di store (per la scrittura del dato) e di load (per la
lettura). Il funzionamento della memoria in linea del tutto generale alquanto
semplice. La CPU indica preventivamente lindirizzo del registro interessato
dalloperazione; la memoria decodifica tale indirizzo abilitando solo il registro ad
esso corrispondente affinch:
- per uno store copi il dato del buffer nel registro;
- per un load copi il dato del registro nel buffer.
dove il buffer pu essere vista come unarea di transito dei dati dalla CPU alla
memoria e viceversa.
Le operazioni di load e store richiedono tempi di attuazione che dipendono
dalle tecnologie usate per la costruzione delle memorie e dalle modalit di accesso.
Le prestazioni di un componente di memoria vengono misurate in termini di tempi
di accesso. Le operazioni di load e store possono avere tempi di accesso differenti.
Il modello di esecutore 49

Nel caso di load, il tempo di accesso misura il tempo che trascorre tra la
selezione del registro di memoria e la disponibilit del suo contenuto nel registro di
buffer. Il tempo di accesso nel caso dello store misura invece il tempo necessario
alla selezione del registro e il deposito del contenuto del registro di buffer in esso.
Le memorie devono mostrare tempi di accesso adeguati alle capacit della CPU,
nel senso che non devono introdurre ritardi quando essa trasferisce dati. Per tale
motivo gli sforzi tecnologici sono rivolti alla costruzione di memorie con tempi di
accesso bassi anche se tale parametro contrasta con quello del costo degli stessi
componenti.

Figura 2 Schema di funzionamento per le operazioni di load e store

La selezione di un registro viene detta:


- casuale quando il tempo di accesso non dipende dalla posizione: memorie
di questo tipo vengono dette RAM (Random Access Memory);
- sequenziale quando invece il tempo di accesso dipende dalla posizione
come avviene nei nastri magnetici.
Alcune memorie vengono realizzate in modo che sia possibile una sola
scrittura di informazioni. Tali memorie vengono dette a sola lettura o ROM (da
Read Only Memory). L'uso di queste memorie necessario quando si desidera che
alcune istruzioni o dati non siano mai alterati o persi. Le memorie composte da
registri sui quali sono consentite le operazioni di lettura e scrittura vengono anche
dette RAM per contrapposizione alle memorie ROM, anche se il termine non
molto appropriato.
Infine si soliti distinguere le memorie in base alla capacit di conservare le
informazioni, anche quando i sistemi che le contengono non sono alimentati. Si
dicono volatili le memorie che perdono le informazioni in esse registrate quando il
50 Capitolo secondo

sistema viene spento; sono, di contro, permanenti gli altri tipi di memorie. Sono
volatili le memorie elettroniche; sono invece permanenti le memorie di tipo
magnetico, ottico e tutti i tipi di ROM.
Lo schema iniziale di Von Neumann stato nel tempo modificato per
affiancare alla memoria centrale delle unit di memoria ausiliarie caratterizzate da
una elevata capacit, dette per questo motivo memorie di massa.

Figura 3 Modello di Von Neumann modificato

La differenza fondamentale fra la memoria centrale e quella di massa, dal


punto di vista funzionale, risiede nel fatto che:
- le informazioni contenute nella memoria centrale possono essere
direttamente prelevate dalla CPU, mentre quelle contenute nella memoria
di massa devono essere dapprima trasferite nella memoria centrale e
successivamente elaborate;
- le informazioni prodotte dalla CPU, viceversa, devono essere depositate
in memoria centrale per poi essere conservate nelle memorie di massa.
Il modello di esecutore 51

Figura 4 Trasferimento di informazioni tra memorie

Unaltra differenza tra la memoria centrale e quella di massa la maggiore


velocit della prima nella gestione dei dati.
Le memorie di massa hanno tempi di accesso maggiori dovuti alle tecnologie
impiegate per realizzarle. Solitamente queste ultime sono di natura magnetica e i
maggiori tempi di accesso si giustificano pensando alle diverse attivazioni di
componenti elettromeccanici necessari a portare i dispositivi di lettura e scrittura
nelle posizioni selezionate. Per ovviare alla differenza di velocit tra i due
dispositivi si impiegano tecniche che prevedono che la memoria centrale non solo
contenga dati e istruzioni ma anche aree di accumulo dei dati in transito verso tutti
i dispositivi esterni. Tali aree vengono dette, come gi anticipato buffer.
Un buffer di input ha quindi il compito di accumulare dati in memoria
ricevendoli da un dispositivo lento prima che la CPU provveda ad elaborarli. Cos
la CPU, solitamente molto pi veloce nelle sue elaborazioni di qualsiasi dispositivo
di output, accumula tutti i dati prodotti in un buffer di uscita prima di abilitarne il
trasferimento. Con i buffer si procede verso una separazione dei compiti tra i
componenti del modello di Von Neumann nellottica di far cooperare dispositivi
caratterizzati da velocit di trattamento dati diverse tra loro. I buffer non sono altro
che magazzini di dati e svolgono le stesse funzioni dei magazzini di una fabbrica,
che regolano i tempi diversi di produzione dei beni da quelli relativi alla loro
vendita: quando la produzione pi veloce la merce si accumula dando tempo ai
venditori di procedere con i loro tempi pi lunghi.
Solitamente le memorie di massa sono di tipo magnetico (nastri, dischi e
tamburi) o ottico (CD, DVD) in modo da mantenere le informazioni in modo
permanente, a differenza delle RAM di tipo elettronico che sono volatili. Le
capacit delle memorie centrali, inoltre, si aggirano sui milioni di informazioni
mentre quelle delle memorie di massa sono dell'ordine delle centinaia o milioni di
milioni.
2.2.2. La CPU
La CPU contiene i dispositivi elettronici in grado di acquisire, interpretare ed
eseguire il programma contenuto nella memoria centrale operando la
trasformazione dei dati. Il processore centrale composto da tre parti
fondamentali: l'Unit di Controllo o Control Unit (CU), l'Unit Logico Aritmetica
(ALU) e un insieme di registri detti interni per distinguerli da quelli della
memoria centrale.
52 Capitolo secondo

Figura 5 Schema interno di una CPU

L'unit di controllo della CPU l'organo preposto all'interpretazione delle


singole istruzioni ed allattivazione di tutti i meccanismi necessari al loro
espletamento. In particolare la CU ha il compito di prelevare ogni istruzione dalla
memoria centrale, di decodificarla, di prelevare i dati dalla memoria se servono
allistruzione, e infine di eseguire listruzione. Per esempio: se l'istruzione
prelevata di tipo aritmetico e richiede due operandi, la CU predispone dapprima il
prelievo dalla memoria di tali operandi, attiva poi l'ALU perch esegua
l'operazione desiderata, ed infine deposita il risultato di nuovo in memoria. Al
termine dell'esecuzione di una istruzione la CU procede al prelievo dalla memoria
della successiva istruzione secondo un ordine rigidamente sequenziale: ossia
lesecuzione di una istruzione pu avere inizio solo se la precedente stata portata
a termine.
Perch lintero sistema possa avere avvio la CU deve essere informata
dell'indirizzo del registro di memoria che contiene la prima istruzione da eseguire.
A partire da questa operazione iniziale detta di boot la CU esegue ininterrottamente
lalgoritmo detto ciclo del processore fino allo spegnimento del sistema.
Le tre fasi del ciclo vengono anche dette fase fetch, operand assembly ed
execute.
Lunit logico aritmetica esegue operazioni aritmetiche, di confronto o bitwise
sui dati della memoria centrale o dei registri interni. Lesito dei suoi calcoli viene
segnalato da appositi bit in un registro chiamato Condition Code. A seconda dei
processori lALU pu essere molto complessa. Nei sistemi attuali lALU viene
affiancata da processori dedicati alle operazioni sui numeri in virgola mobile detti
processori matematici.
Il modello di esecutore 53

Figura 6 Ciclo del Processore

Durante le sue elaborazioni la CU pu depositare informazioni nei suoi


registri interni in quanto sono pi facilmente individuabili e hanno tempi di accesso
inferiori a quelli dei registri della memoria centrale. Il numero e tipo di tali registri
varia a seconda dellarchitettura della CPU.
Quelli che si trovano in molte CPU sono l'Istruction Register (IR), il Prossima
Istruzione (PI), l'Accumulatore (ACC) e il Condition Code (CC).
Il primo contiene l'istruzione prelevata dalla memoria e che l'unit di controllo
sta eseguendo. Il PI invece ricorda alla CU la posizione in memoria della
successiva istruzione da eseguire. Nei casi in cui ogni registro di memoria
contenga unintera istruzione, e l'insieme delle istruzioni del programma sia
disposto ad indirizzi consecutivi, la CU incrementa di uno il valore contenuto in PI
dopo ogni prelievo di una istruzione dalla memoria. L'ACC serve come deposito di
dati da parte dell'ALU nel senso che contiene prima di unoperazione uno degli
operandi, e al termine della stessa operazione il risultato calcolato. In questo caso i
registri Op1 e Op2 diventano interni allALU. Il CC indica le condizioni che si
verificano durante l'elaborazione, quali risultato nullo, negativo e overflow.
2.2.3. I bus
La CPU comunica con la memoria e tutti i dispositivi di input ed output tramite tre
canali detti anche bus. I bus collegano due unit alla volta abilitandone una alla
trasmissione e laltra alla ricezione: il trasferimento di informazioni avviene sotto il
controllo della CPU.
Il termine bus indica un canale di comunicazione condiviso da pi utilizzatori.
Esso fisicamente costituito da uno o pi fili su cui possono transitare uno o pi
bit contemporaneamente. A seconda delle informazioni trasportate si distinguono
in:
- bus dati (data bus)
54 Capitolo secondo

- bus indirizzi (address bus)


- bus comandi o di controllo (command o control bus)

Figura 7 I bus

Il control bus serve alla C.U. per indicare ai dispositivi cosa essi devono fare.
Tipici segnali del control bus sono quelli di read e write mediante i quali la CU
indica ai dispositivi se devono leggere un dato dal data bus (read) o scriverlo su di
esso (write).

Figura 8 Control Bus

Il data bus permette ai dati di fluire da CPU a registro di memoria selezionato


per operazioni di store e viceversa per quelle di load. La CU controlla anche il
flusso di informazioni con il mondo esterno abilitando il transito delle informazioni
dalla memoria verso le risorse di output e viceversa da quelle di input.
Nello schema le memorie di massa sono rappresentate in un blocco a parte
solo per la loro importanza come memoria permanente del sistema, anche se il loro
funzionamento quello di un dispositivo che opera sia in input che in output.
Laddress bus serve alla CU per comunicare l'indirizzo del dispositivo interessato
da una operazione di lettura o scrittura. In questa ottica anche i dispositivi di input
Il modello di esecutore 55

od uno di output sono identificati da un indirizzo alla stessa stregua dei registri di
memoria.
Tutti i componenti del sistema (memoria, input, output, memoria di massa,
etc.) devono essere dotati della capacit di riconoscere sulladdress bus il proprio
indirizzo. In altri termini attraverso laddress bus la CU effettua la selezione del
dispositivo a cui sono rivolti i comandi e i dati.
I bus realizzano lo scambio di informazioni tra tutti i componenti
caratterizzato dalle seguenti regole:
- la CPU lunico elemento che fornisce lindirizzo alladdress bus;
- memorie e dispositivi di input ed output devono ascoltare laddress bus
per attivarsi quando su di esso compare il proprio indirizzo identificativo;
nel caso della memoria lattivazione avviene quando viene riconosciuto
lindirizzo corrispondente ad uno dei registri di cui essa composta;
- il dispositivo attivo deve interpretare i segnali del control bus per eseguire
i comandi della CU;
- le memorie prelevano dati dal data bus o immettono dati in esso in
funzione del comando impartito dalla CU;
- i dispositivi di input possono solo immettere dati sul data bus;
- viceversa i dispositivi di output possono solo prelevare dati dal data bus.
Un bus costituito da un solo filo chiamato bus seriale e su di esso i bit
transitano uno dietro laltro. Un bus costituito da n fili chiamato bus parallelo
perch su di esso transitano n bit alla volta. Tipici sono i bus a 8 e 32 fili sui quali
si possono trasferire rispettivamente 8 e 32 bit (4 Byte) alla volta.
Laddress e il data bus sono paralleli e le loro dimensioni caratterizzano i
sistemi di calcolo. Il numero di bit delladdress bus indica la capacit di
indirizzamento della CPU: ossia la sua capacit di gestire la dimensione della
memoria centrale e il numero di dispositivi di input ed output. Infatti un address
bus con n bit consente di selezionare un registro tra 2n. La dimensione del data bus
condiziona invece la velocit di scambio delle informazioni tra i diversi dispositivi
in quanto con m fili solo m bit possono viaggiare contemporaneamente.
2.2.4. Il clock
I componenti del modello di Von Neumann vengono coordinati dalla CU della
CPU secondo sequenze prestabilite che corrispondono alle sue diverse capacit. Ad
ogni operazioni che la CU in grado di svolgere corrispondono ben prefissate
sequenze di attivazione dei diversi dispositivi. Le attivit di tutti i dispositivi non si
svolgono per casualmente ma vengono sincronizzate tra loro mediante un
orologio interno chiamato clock che scandisce i ritmi di lavoro.
Il clock un segnale periodico di periodo fisso, unonda quadra caratterizzata
da un periodo T (detto ciclo) e da una frequenza f (f=1/T) misurata in Hertz (Hz).
Ad esempio un clock composto da 10 cicli al secondo ha la frequenza f = 10 Hz e il
periodo T= 100ms. La frequenza dei clock presenti nei moderni sistemi spazia dai
MHz (1 MHz corrisponde a un milione di battiti al secondo) ai GHz (1 GHz
corrisponde a un miliardo di battiti al secondo).
Il clock un segnale che raggiunge tutti i dispositivi per fornire la cadenza
temporale per lesecuzione delle operazioni elementari.
56 Capitolo secondo

Figura 9 Clock

La velocit di elaborazione di una CPU dipende dalla frequenza del suo clock
come il suono prodotto da un musicista dipende dal metronomo: pi accelerato il
battito del clock maggiore la velocit di esecuzione.
Alla frequenza del clock legato il numero di operazioni elementari che
vengono eseguite nellunit di tempo dalla CU. Ad esempio, se si assume che ad
ogni ciclo di clock corrisponde esattamente lesecuzione di una sola operazione,
allora la frequenza del clock indica il numero di operazioni che vengono eseguite
nellunit di tempo dalla CU. Per esempio con un clock a 3 GHz si ha che il
processore in grado di eseguire 3 miliardi di operazioni al secondo.
In realt tale ipotesi non sempre vera in quanto lesecuzione di una
operazione pu richiedere pi cicli di clock sia per la complessit delle operazioni
che per la lentezza dei dispositivi collegati alla CPU.
Per comprendere limportanza del clock si osservi il trasferimento di dati e
istruzioni tra CPU e Memoria Centrale riportato in una forma semplificata in figura
10.

Figura 10 Trasferimento dati tra CPU e Memoria


Il modello di esecutore 57

Entrambe le operazioni di load e store avvengono sotto lo stretto controllo


della CU. Nel primo caso la CU procede eseguendo nellordine:
- con il primo battito di clock ponendo lindirizzo del registro di memoria
di cui vuole leggere il contenuto sulladdress bus;
- con il secondo battito segnalando alla memoria che si tratta di una
operazione di read;
- con il terzo battito prendendo il dato dal data bus dove la memoria ha
provveduto a depositarlo.
Nel caso di una operazione di store la CU provvede:
- con il primo battito di clock a porre lindirizzo del registro di memoria di
cui vuole leggere il contenuto sulladdress bus;
- con il secondo battito a depositare il dato sul data bus;
- con il terzo battito a segnalare alla memoria che si tratta di una operazione
di write e quindi che il dato pronto per essere depositato nel registro
selezionato.
Nellesempio si fatta lipotesi che la memoria sia in grado di gestire sia
linserimento del dato sul data bus che il prelievo del dato da esso in un solo ciclo
di clock. Nella realt le memorie possono essere pi lente introducendo ritardi che
portano la stessa operazione a completarsi con un maggior numero di clock. La
memoria centrale infatti realizzata mediante moduli che hanno prestazioni
decisamente inferiori rispetto alla tecnologia utilizzata per costruire le CPU; per
questo motivo si realizzano dei bus che rallentano la trasmissione di un fattore 10
rispetto al clock.

2.3. Firmware, software e middleware


Lo scopo del modello di Von Neumann quello di eseguire i comandi memorizzati
nella sua memoria centrale. I comandi prendono anche il nome di istruzioni non
solo perch istruiscono la CPU sul da farsi ma anche per effettuare una distinzione
con i dati che sono gli oggetti rappresentati in memoria centrale su cui si svolgono
le attivit. Linsieme delle istruzioni prende il nome di programma. Tutti i
programmi sono quindi formati da insiemi di istruzioni che la CU della CPU
esegue mediante il coordinamento di tutti i componenti del modello di Von
Neumann.
Le istruzioni sono operazioni semplici quali:
- trasferimento dati da un registro ad un altro (da memoria a memoria, da
memoria a registri della CPU o viceversa, da memoria a output, da input a
memoria);
- operazioni aritmetiche o logiche eseguite dallALU;
- controllo di condizioni riportate dal registro CC o deducibili dal confronto
di due registri.
La CU un automa che ripete senza sosta il prelievo di una istruzione dalla
memoria e la sua interpretazione con relativa esecuzione. Lesecuzione di una
istruzione da parte della CU consiste nellinoltro di una sequenza di abilitazioni dei
dispositivi il cui effetto corrisponde alla operazione richiesta. Le prime CU erano
realizzate con circuiti, detti a logica cablata, che evolvevano in tanti modi diversi
quante erano le istruzioni che essa era in grado di svolgere.
58 Capitolo secondo

Figura 11 CU a logica cablata

Le moderne UC sono invece realizzate in logica microprogrammata. Ad ogni


istruzione corrisponde una sequenza di microistruzioni conservate in una memoria
interna alla CU. La sequenza di microistruzioni ha il compito di generare le
abilitazioni necessarie alla attuazione della istruzione. A tal fine un circuito interno
alla UC provvede alla generazione di indirizzi per individuare una dopo laltra le
microistruzioni che un decodificatore trasforma in segnali di abilitazione.
Listruzione nel registro IR determina la posizione della prima microistruzione.

Figura 12 CU a logica micorprogrammata

In effetti si ripropone in piccolo il modello di Von Neumann per la


realizzazione del suo componente principale. Linsieme dei microprogrammi
composti dalle microistruzioni memorizzate nella memoria interna alla CU prende
il nome di firmware.
Lesecuzione di un qualsiasi programma si traduce nel far applicare la CPU su
un compito specifico interagendo con il mondo esterno tramite i dispositivi di input
ed output. Tipiche applicazioni dei computer sono lelaborazione di documenti, la
ricerca di informazioni in Internet, il foglio elettronico, la gestione della segreteria
studenti di una universit, il sistema operativo a finestre. Linsieme di tutte le
applicazioni del computer, quindi di tutti i programmi per computer, prende il
nome di software. In una accezione pi ampia il termine software pu essere inteso
Il modello di esecutore 59

come tutto quanto pu essere preteso dallhardware: basta infatti inserire in


memoria un programma diverso perch il sistema cambi le sue attivit. Tra tutte le
macchine automatiche il computer un sistema polifunzionale in quanto pu
eseguire infinite funzioni sempre che venga progettato un programma per ogni
applicazione.

Figura 12 Hardware, Firmware e Software


Nel variegato ed immenso mondo di applicazioni dei computer si soliti fare
una distinzione tra i programmi che servono a tutti gli utenti del sistema da quelli
che risolvono problemi specifici. I primi vengono classificati come software di
base e riguardano i sistemi operativi e i traduttori dei linguaggi di
programmazione. I programmi che non rientrano in tale categoria vengono detti del
software applicativo.

Figura 13 Software di base ed applicativo

Tra le applicazioni del software di base pi importanti si trova il sistema


operativo. Senza di essa gli elaboratori non mostrerebbero quella semplicit di uso
che ne sta caratterizzando la diffusione estesa nella societ. Il sistema operativo
un insieme di programmi che deve garantire la gestione delle risorse hardware in
modo semplice ed efficiente a tutti gli utenti del sistema, siano essi persone che
interagiscono tramite tastiera e monitor che altre applicazioni. I primi calcolatori
non avevano il sistema operativo. In essi il programmatore doveva prevedere tutto:
dai calcoli alla gestione dei dispositivi di input ed output; doveva anche provvedere
al caricamento del programma in memoria prima di attivare la CPU perch lo
60 Capitolo secondo

eseguisse. Al termine dellesecuzione del programma il programmatore o


loperatore del sistema doveva provvedere ad un nuovo caricamento in memoria ed
ad una successiva attivazione. Con il sistema operativo il passaggio da una
applicazione ad unaltra svolto in automatico mediante linterpretazione di
comandi che lutente inserisce da tastiera. La CPU si trova cos ad eseguire i
programmi del sistema operativo in alternanza con quelli applicativi.

Figura 14 Schema di funzionamento di un Sistema Operativo


I programmi del sistema operativo vengono eseguiti allavvio del sistema,
quando termina unapplicazione o quando una applicazione ha bisogno di gestire
una risorsa hardware.
Altre applicazioni del software di base stanno sempre pi assumendo un
rilievo particolare, soprattutto in relazione al funzionamento di pi sistemi tra loro
interconnessi attraverso canali di comunicazione di cui Internet il pi importante
esempio. Per far s che i sistemi possano mostrarsi uguali tra loro si sovrappone al
sistema operativo uno strato software chiamato middleware che ha il compito di
interagire con lapplicazione utente. Il middleware il software che fornisce
unastrazione di programmazione che maschera leterogeneit degli elementi
sottostanti (reti, hardware, sistemi operativi, linguaggi di programmazione) e la
loro distribuzione tra i diversi nodi della rete. Il middleware definisce una
macchina generalizzata fissandone modalit di interazione con le applicazioni.

Figura 15 Middleware
Il modello di esecutore 61

2.4. Evoluzione del modello di Von Neumann


Nel tempo sono state apportate modifiche al modello di Von Neumann con lo
scopo di rendere il suo funzionamento pi veloce. Il primo intervento ha riguardato
la struttura dei dispositivi di input ed output. Nei primi sistemi di calcolo la CPU
controllava tutti i componenti del sistema, anche quelli interni ai dispositivi
periferici. Inoltre, data la natura rigidamente sequenziale del modello di Von
Neumann, non era possibile sovrapporre i tempi delle operazioni di input con
quelli delloutput. Per ovviare a tali limitazioni, sono stati realizzati sistemi
dedicati il cui compito scaricare la CPU della gestione di attivit specifiche. I
sistemi dedicati, detti anche canali, con la loro autonomia possono lavorare anche
contemporaneamente con la CPU. I primi canali introdotti sono stati quelli di input
ed output. Oggi esistono in un elaboratore processori dedicati alla grafica, alle
operazioni sui numeri reali, alla acquisizione di segnali analogici.
Da soli i canali non possono per garantire la piena autonomia di
funzionamento. Per rendere indipendenti i processori dedicati stato introdotto
nellarchitettura hardware un segnale detto delle interruzioni mediante il quale una
qualsiasi entit esterna alla CPU pu richiederle attenzione.

Figura 16 - Interruzioni

Con la presenza del segnale di interruzione la CPU pu attivare un processore


periferico e disinteressarsi delle sue attivit. Quando un processore dedicato
termina il suo compito, avanza una richiesta di interruzione al processore centrale e
aspetta che gli venga rivolta attenzione. Mentre i processori periferici lavorano, la
CPU pu lavorare anchessa a meno che non sia indispensabile quanto richiesto
allo specifico processore: in questo caso la CPU aspetta che il processore concluda
quanto richiesto. Solitamente le attivit svolte dai processori dedicati sono lente,
per cui la CPU pu svolgere attivit diverse dando limpressione allutente esterno
di farle contemporaneamente. Le richieste di interruzione sono diverse, tante quanti
sono i processori dedicati, e si verificano in momenti diversi ed in modo non
sincrono con il lavoro della CPU. Per consentire alla CU di accorgersi del
verificarsi di una interruzione il registro di condizione CC stato dotato di un bit
che diventa uguale ad uno quando arriva una interruzione. La CU controlla il bit al
termine delle esecuzione di ogni istruzione: se uguale zero procede normalmente
con il prelievo dellistruzione successiva; in caso contrario comincia lesecuzione
di un programma del sistema operativo, detto ISR (interrupt service routine) che ha
come compito primario di capire la causa della interruzione, ossia quale dispositivo
ha avanzato la richiesta. Nel caso si accorga della presenza di pi richieste
62 Capitolo secondo

stabilisce quale servire per prima secondo criteri di importanza o priorit di


intervento.

Figura 17 Modifica del ciclo del processore


Con i canali e il sistema delle interruzione si introdotto una prima forma di
parallelismo nello svolgere le diverse attivit richieste da un programma. Ad
esempio la CPU dopo aver attivato il canale di output affinch stampi una
sequenza di dati, pu attivare il canale di input perch prelevi da tastiera un
insieme di dati. Nellattesa che i due canali segnalino di aver terminato, la CPU
pu procedere in parallelo eseguendo altre operazioni. La gestione dei canali
svolta dai programmi del sistema operativo, come del resto la gestione di tutti i
componenti del modello di Von Neumann, memoria compresa.
Anche alla memoria centrale sono stati apportati cambiamenti per evitare che
la CPU si dovesse adeguare ai tempi pi lenti di gestione dei dati da essa garantiti.
Si fino ad oggi verificato che componenti di memoria veloci avessero costi che
ne impedivano una significativa presenza allinterno dei sistemi. Per tale motivo,
per ridurre i tempi di trasferimento dalla memoria centrale ai registri interni della
CPU, viene replicata una porzione di memoria e posta tra memoria e CPU stessa.
Tale memoria, molto veloce, viene chiamata cache e fa da buffer per il prelievo di
informazioni dalla memoria centrale. Con operazioni particolari istruzioni e dati
vengono trasferiti dalla memoria centrale nella cache secondo la capacit di
questultima. La CU procede nelle tre fasi del suo ciclo al prelievo di istruzioni e
operandi dalla cache. Quando la CU si accorge che il prelievo non pu avvenire
scatta un nuovo travaso dalla memoria centrale. Poich la cache molto pi veloce
della memoria centrale il sistema ne guadagna complessivamente in efficienza.
Dato lelevato costo dei componenti con i quali si realizzano le cache, si
soliti riscontrare processori con 256k byte o 512 k byte di memoria cache. Solo
sistemi che devono garantire elevate prestazioni in applicazioni critiche presentano
cache da 1Mbyte ed oltre. Se la cache interna alla CPU viene detta di primo
livello (L1); le cache di secondo livello (L2) sono invece esterne e solitamente un
p pi lente di quelle di primo livello ma sempre pi veloci della memoria centrale.
Infatti la cache L2 risulta 4 o 5 volte pi lenta della cache L1 mentre la RAM lo
addirittura 20 o 30 volte. I due livelli possono coesistere.
La memoria viene cos ad essere strutturata in maniera gerarchica. La
gerarchia consente di offrire ai programmi lillusione di avere una memoria grande
Il modello di esecutore 63

e veloce. Nella gerarchia i livelli pi prossimi alla CPU sono anche quelli pi
veloci. Ma sono anche quelli con dimensioni pi piccole visto il loro elevato costo.
Invece, quelli pi lontani sono quelli che mostrano una capacit massima ed anche
tempi di accesso maggiori. Partendo dalla CPU ogni livello fa da buffer al livello
successivo.

Figura 18 Gerarchia di memorie

2.5. Il modello astratto di esecutore


La macchina di Von Neumann il modello di riferimento che consente di
comprendere le modalit con le quali un elaboratore esegue in modo automatico
una qualsiasi applicazione pensata per esso. Il modello si basa sul concetto di
automa capace di eseguire un programma residente nella memoria centrale. In
generale un programma un insieme di istruzioni la cui descrizione dipende dalle
capacit di comprensione di un linguaggio da parte del componente che svolge le
funzioni di CPU. La CPU, come automa capace di interpretare un prefissato
linguaggio, esemplifica il comportamento di un qualsiasi esecutore di programmi,
sia esso umano o macchina. Tra tutti i linguaggi di programmazione il linguaggio
macchina quello direttamente interpretabile da una CPU reale presente in un
sistema informatico. Dato il suo basso potere espressivo anche detto linguaggio
di basso livello.
Linsieme di istruzioni (repertorio) che si possono descrivere comprendono
operazioni che:
- spostano stringhe di bit da un registro allaltro di memoria;
- attivano lALU per effettuare la somma aritmetica, lAND, lOR tra
coppie di stringhe di bit, o la negazione (NOT) del contenuto
dellaccumulatore;
- eseguono lo scorrimento o la rotazione a destra o a sinistra dei bit
contenuti nellaccumulatore;
- provvedono ad interrogare i bit del registro di condizione (CC) per
determinare come procedere nellesecuzione del programma;
- consentono di saltare ad un qualsiasi punto del programma.
Tutte le istruzioni del programma devono essere allocate nei registri di
memoria prima che la CPU possa procedere alla loro interpretazione ed
esecuzione. Lo schema di allocazione deve essere definito dal programmatore
rispettando un principio fondamentale secondo cui deve sempre esistere una
64 Capitolo secondo

separazione netta tra le porzioni di memoria occupate dai dati da quelle occupate
dalle istruzioni. Un tale principio serve a garantire che in ogni istante la CPU stia
eseguendo effettivamente il programma progettato dal programmatore. Infatti
lallocazione in memoria di dati e programmi pu essere statica o dinamica. Nel
primo caso lallocazione avviene prima dellinizio dellesecuzione del programma;
nel secondo durante la sua esecuzione. Nel caso di allocazione dinamica di dati pu
avvenire che, per errori di programmazione, alcuni dati vadano ad allocarsi
nellarea riservata alle istruzioni cambiando il contenuto dei registri di memoria e
cambiando di fatto la struttura del programma.
Lallocazione in memoria comporta unassociazione precisa tra istruzioni e
dati e registri. In un modello di memoria a voce ad ogni istruzione o dato
corrisponde un ed un solo registro di memoria. Nelle memorie a byte istruzioni o
dati possono occupare pi registri di memoria. Per semplicit si proceder
pensando ad una memoria organizzata a voce.
Il riferimento ad una istruzione o ad un dato avviene specificando lindirizzo
di memoria occupato. Lindicazione di un indirizzo di memoria contenente un dato
si dir puntatore a dato, il puntatore a istruzione invece un indirizzo di un registro
di memoria nel quale collocata una istruzione.
Si pu allora definire una istruzione in linguaggio macchina come una
quadrupla: i = (Cop, Pdi, Pdo, Pis), in cui:
- Cop il codice operativo, ossia il codice che indica alla UC della CPU
loperazione da compiere; linsieme dei Cop prendere il nome di
repertorio di istruzioni e dipende dalla specifica CPU;
- Pdi sono i puntatori ai dati che servono per svolgere loperazione Cop detti
anche di input; si noti che esistono istruzioni che non hanno operandi di
input;
- Pdo sono i puntatori ai dati prodotti dalloperazione Cop detti anche di
output; si noti che esistono istruzioni che non hanno operandi di output;
- Pis il puntatore allistruzione da svolgere al termine dellesecuzione di
quella corrente.
Il puntatore Pis serve a comprendere la corrispondenza tra struttura di un
programma e schema di allocazione in memoria. Un programma una sequenza di
istruzioni da svolgere una dopo laltra. Ogni istruzione occupa un registro di
memoria. Il puntatore Pis serve a legare tra loro i registri di memoria contenenti
istruzioni in modo che sia chiara la sequenza del programma.
Il modello di esecutore 65

Figura 19 Esecuzioni di programmi nel modello astratto di processore

La definizione della struttura dellistruzione e dello schema di allocazione dei


programmi in memoria permettono di dettagliare le fasi del ciclo del processore.
La fase fetch inizia con il prelievo dellistruzione dalla memoria. Per farlo la CU
comunica alla memoria il valore del puntatore ad istruzione presente nel registro
PI. La risposta della memoria viene depositata nel registro IR cos da consentire
alla CU di:
- interpretare il codice operativo dellistruzione da eseguire;
- conoscere i puntatori ai dati di input ed output;
- ricevere il puntatore allistruzione da eseguire successivamente.
La fase fetch si conclude con laggiornamento del registro PI con il valore del
puntatore allistruzione successiva presente in IR.
La fase operand assembly serve alla CU per predisporre gli operandi che
servono al codice operativo. Per farlo la CU usa i puntatori ai dati contenuti nel
registro IR.
La fase execute consiste nel mettere in essere le azioni richieste con il codice
operativo presente nel registro di IR. Nel caso vengano prodotti risultati, ne verr
effettuata la memorizzazione negli indirizzi specificati dai puntatori ai dati di
output presenti nel registro IR.

Figura 20 Aggiornamento registri CPU


66 Capitolo secondo

Al termine dellesecuzione di una istruzione la CU riprende il suo cammino


partendo dal contenuto del registro PI. Le istruzioni del programma vengono
eseguite una dopo laltra indipendentemente dalla loro disposizione in memoria. Si
pu allora introdurre una notevole semplificazione imponendo al programmatore di
disporre le istruzioni ad indirizzi consecutivi di memoria e facendo in modo che la
CU nella fase fetch aggiorni il PI semplicemente incrementando di uno il suo
contenuto. In tale modalit le istruzioni non devono pi riportare il Pis in quanto
implicitamente il suo valore dato dal valore del registro PI pi uno.

Figura 21 Aggiornamento registri CPU con istruzioni consecutive in memoria

Perch il ciclo del processore possa avere inizio si deve predisporre in modo
che il registro PI contenga lindirizzo del registro di memoria contenente la prima
istruzione da eseguire. La fase iniziale di boot ha solo il compito di inizializzare il
PI con tale valore. Una volta avviato, il ciclo del processore non termina mai e
quindi ad ogni istruzione deve sempre seguirne unaltra da eseguire
successivamente. Questo spiega perch quando termina unapplicazione un
elaboratore torna ad eseguire i programmi del sistema operativo.
Dai programmi del sistema operativo si passa ad unaltra applicazione in
unalternanza che fa s che la CU possa procedere con il suo ciclo. Ma perch tutto
ci proceda nel rispetto del modello di Von Neumann, deve avvenire che in
memoria siano sempre presenti i programmi e i dati del sistema operativo mentre
quelli delle applicazioni vengono caricati in memoria dal sistema operativo su
richiesta dellutente prima che ne venga attivata la esecuzione. Nella memoria di
un elaboratore moderno si possono pertanto individuare in ogni istante cinque aree
distinte:
- linsieme dei registri nei quali si trovano i programmi del sistema
operativo;
- quelli occupati dai dati del sistema operativo;
- quelli nei quali si trovano le applicazioni di utente;
- quelli con i dati dei programmi di utente;
- ed infine linsieme dei registri che servono come buffer per il
trasferimento dati da e verso i dispositivi di input ed output.
Il modello di esecutore 67

Figura 22 Programmi in memoria

Il modello di Von Neumann pu essere generalizzato. La memoria si pu


sostituire con un foglio di lavoro sul quale sono segnate le istruzioni. Il foglio serve
anche per scrivere i dati. Lesecutore un processore di Von Neumann se in
grado di:
- leggere le istruzioni una alla volta dal foglio di lavoro;
- interpretare il linguaggio con il quale le istruzioni sono scritte;
- leggere e scrivere i dati sul foglio di lavoro;
- compiere le azione prescritte dalle istruzioni.

2.6. I microprocessori
Come gi descritto, un processore centrale (CPU) conforme al modello di Von
Neumann si compone logicamente di:
- una unit di controllo (UC) capace di interpretare i comandi ad esso
rivolti (detti anche istruzioni), di svolgere le azioni richieste da tali
comandi e di interagire con lambiente esterno attraverso dispositivi
periferici capaci di trasformare i segnali in modo tale che siano
comprensibili dalla UC da una parte, e, dagli utenti del sistema dallaltra;
- una unit logico aritmetica (ALU) per l'esecuzione delle operazioni di
tipo aritmetico (solitamente somma) e logico (somma, prodotto e
negazione logici).

Figura 23 Schema di una CPU


68 Capitolo secondo

L'architettura di Von Neumann viene detta di tipo SISD (Single Istruction


stream, Single Data stream) in quanto le istruzioni di un programma vengono
eseguite una dopo laltra serialmente con lunit di controllo che interpreta le
singole istruzioni generando comandi per tutti gli altri dispositivi, e con l'unit di
elaborazione che esegue le operazioni di tipo aritmetico o logico.
La disponibilit di registri interni come propri organi di memoria per compiti
speciali e/o per contenere informazioni temporanee, consente di ridurre i tempi di
esecuzione delle istruzioni non solo perch si riducono gli accessi alla memoria
centrale ma anche perch i trasferimenti interni al processore sono pi veloci. Il
programma e i dati sono contenuti in memoria ed una singola istruzione viene
eseguita mediante le seguenti fasi:
1. lettura dalla memoria dell'istruzione da eseguire;
2. determinazione dell'indirizzo della successiva istruzione da eseguire;
3. determinazione del significato del codice operativo per individuare
lazione da eseguire;
4. eventuale determinazione degli indirizzi degli operandi;
5. esecuzione delle sequenze di operazioni elementari richieste dalla
istruzione.
Il processore una macchina sequenziale capace di svolgere unazione alla
volta: pertanto unistruzione viene eseguita in passi successivi con azioni
elementari abilitate dal segnale di tempificazione (clock) e il suo tempo di
esecuzione dipende dal numero di passi per eseguirla e dalla frequenza del clock.
I microprocessori sono dispositivi elettronici in grado di contenere allinterno
di un unico circuito integrato le funzioni di unintera CPU. Il microprocessore
interagisce con tutti gli altri dispositivi attraverso i collegamenti dei bus di dati
(data bus), di indirizzo (address bus) e di controllo (control bus). I microprocessori
hanno bus dati a 8, 16, 32, 64 bit. La dimensione del bus dati esprime la capacit
di elaborazione del processore, ossia la quantit di bit che possono essere elaborati
in parallelo. Il bus indirizzi esprime, di contro, la capacit di memorizzazione del
processore, ossia il numero di celle diverse a cui si pu accedere (2m celle di
memoria, se m il numero dei bit del bus). La tabella riporta alcuni valori tipici del
parallelismo esterno.

Bit Data BUS Bit Address BUS Capacit di indirizzamento


8 16 64 KByte
16 20-24 1-16 MByte
64 64 fino a circa 1019 Byte

Tabella 1 Capacit di indirizzamento

Molti sono oggi i microprocessori presenti sul mercato ed una loro


classificazione pu essere fatta sulla base dei seguenti parametri:
- parallelismo esterno espresso come numero di bit trasferiti o prelevati in
un singolo accesso in memoria (8, 16, 32, 64,...) e caratterizzanti quindi il
suo data bus;
- capacit di indirizzamento legata alla dimensione in bit del suo address
bus
Il modello di esecutore 69

- numero, tipo e parallelismo dei registri interni;


- tecniche di indirizzamento intese come la modalit con la quale costruire
lindirizzo logico con il quale prelevare o salvare il valore delloperando
di una istruzione;
- gestione delle periferiche di input ed output;
- repertorio delle istruzioni inteso come numero e tipo di istruzioni
costituenti il suo linguaggio macchina;
- tempi necessari allesecuzione di alcune istruzioni fondamentali come
l'addizione da utilizzare per la valutazione del MIPS con il quale
effettuare confronti sulle prestazioni.
I microprocessori sono quindi caratterizzati anche dal numero e tipo di registri
interni di cui sono dotati. Nel tempo il numero di registri interni aumentato per
rendere pi veloce lesecuzione delle istruzioni consentendo lattivazione in
parallelo di alcune microoperazioni della unit di controllo. Linsieme minimo di
registri interni che sicuramente presente in qualsiasi microprocessore moderno
formato da:
- Prossima Istruzione (PI) o anche Program Counter: che nella fase fetch
cambia il proprio contenuto passando da puntatore in memoria
allistruzione da prelevare allinizio della fase a puntatore allistruzione
successiva da eseguire al termine della stessa;
- Accumulatore (ACC): utile a conservare dati temporanei o necessario per
attivare le operazioni logiche ed aritmetiche dellALU; infatti attraverso
esso i dati vengono forniti allALU prima di una operazione e in esso si
trova il risultato delloperazione eseguita; alcuni microprocessori possono
presentare anche due accumulatori;
- Condition Code (CC): che riporta lo stato dellelaborazione indicato dai
diversi bit di cui si compone:
o Bit Segno (S): indica con 1 la presenza in ACC di un valore
negativo; con 0 il caso opposto;
o Bit Zero (Z): indica con 1 la presenza in ACC di un valore uguale
a zero; con 0 il caso opposto;
o Bit Overflow (O): indica con 1 la presenza in ACC di un valore
non corretto in quanto loperazione aritmetica ha generato un
risultato con pi bit di quelli rappresentabili; con 0 il caso
opposto;
o Bit Riporto o Carry (C): indica con 1 la presenza in ACC di un
risultato che ha generato un ulteriore bit nel calcolare una
somma; con 0 il caso opposto;
o Bit Interruzione (I): indica con 1 che il sistema delle interruzioni
ha generato una richiesta di attenzione; con 0 il caso opposto;
- Registo Indice (X): con il quale poter calcolare lindirizzo delloperando
con quella che sar chiamata tecnica di indirizzamento relativa;
- Stack Pointer (SP): utile alla gestione del salti a sottoprogrammi per il suo
modo di gestire gli indirizzi di memoria; lo stack pointer contiene infatti
un puntatore alla memoria ad una area che viene chiamata stack;
attraverso SP si accede a tale area con operazioni di inserzione (PUSH) o
di estrazione (POP) con la tecnica LIFO (last in first out), che comporta
che lultimo elemento inserito sia anche il primo ad essere estratto.
70 Capitolo secondo

Laccumulatore si interfaccia direttamente con il data bus presentando la


stessa dimensione in numero di bit. I registri prossima istruzione, indice e stack
pointer sono collegati invece con laddress bus al quale comunicano lindirizzo
dopo averlo costruito secondo quanto richiesto dalle diverse istruzioni.

Figura 24 Registri interni di un microprocessore

La figura 24 illustra le dimensioni dei registri in un architettura classica che


vede il parallelismo del data bus fissato ad 8 bit ed una capacit di indirizzamento
(parallelismo delladdress bus) a 16 bit. I moderni processori operano con registri
di accumulatore a 16 o 32 bit e parallelismo delladdress bus a 32 o 64 bit.
A tali registri fondamentali si soliti aggiungere tre ulteriori registri:
- Istruction Register (IR): contenente al termine della fase fetch listruzione
prelevata dalla memoria centrale completa di tutte le sue parti (codice operandi
e puntatori ad operandi se necessari);
- Data buffer (DB): interfaccia con il data bus del quale rappresenta lo stato, in
altri termini il registro DB indica il valore che assume il data bus;
- Address buffer (AB): interfaccia con laddress bus del quale rappresenta lo
stato, in altri termini il registro AB indica il valore che assume laddress bus.
Tali tre registri vengono introdotti per schematizzare il comportamento della
CPU nella sua interazione con la memoria centrale. Essi riportano lo stato del
processore durante le diverse fasi (fetch, operand assembly ed execute) del suo
ciclo.

Figura 25 Schema di una CPU


Il modello di esecutore 71

In figura 25 si riporta larchitettura di riferimento di un processore dotato


dellinsieme di registri indicati con le principali connessioni tra essi.
Nella costruzione dellindirizzo di un operando di una istruzione le tecniche di
indirizzamento pi diffuse sono:
- indirizzamento immediato che indica che il valore contenuto gi
nellistruzione;
- indirizzamento diretto con il quale viene riportato nellistruzione lindirizzo
del registro di memoria che contiene il valore o nel quale depositare il valore;
- indirizzamento indiretto che riporta nellistruzione lindirizzo del registro di
memoria al cui interno specificato lindirizzo del registro dal quale prelevare
un valore o nel quale depositare un valore;
- indirizzamento relativo con il quale lindirizzo del registro di memoria che
contiene il valore o nel quale depositare il valore specificato nel registro
interno del processore detto indice.
Le diverse tecniche di indirizzamento vengono indicate nellistruzione, o
diversificando il codice operativo, o aggiungendo dei bit appositi il cui valore
indica alla UC come costruire lindirizzo.
Ad esempio per la semplice istruzione per il caricamento dellaccumulatore si
potrebbero avere in linguaggio macchina (rappresentato in esadecimale) le quattro
istruzioni di tabella:

Cod Operando Tecnica Commento Accessi in memoria


Op. nella fase
Operand Assembly
60 00ff Immediata LOAD ACC con il valore nessuno in quanto il dato
255 prelevato nella fase fetch
con listruzione
61 00ff Diretta LOAD ACC con il un solo accesso in
contenuto del registro di memoria
memoria di indirizzo 255
62 00ff Indiretta LOAD ACC con il due accessi in memoria: il
contenuto del registro di primo per prelevare
memoria il cui indirizzo lindirizzo alla posizione
contenuto nel registro di indicata; il secondo per
indirizzo 255 prelevare il dato
63 Relativa LOAD ACC con il un solo accesso in
contenuto del registro di memoria
memoria il cui indirizzo
presente nel registro indice
X

Tabella 2 Esempi di indirizzamento

In uno stato del processore come quello riportato in figura 26, gli effetti delle
diverse tecniche di indirizzamento per quanto attiene alla esecuzione della
istruzione di caricamento dellaccumulatore sono mostrati in figura 27.
72 Capitolo secondo

Figura 26 Stato iniziale della CPU

Immediato Diretto

Indiretto Relativo

Figura 26 Effetti delle tecniche di indirzzamento

Nella gestione delle periferiche di input ed output (I/O) i microprocessori si


dividono in quelli che usano la tecnica memory-mapped e in quelli che invece
adottano lI/O-mapped. Con la tecnica memory mapped lUC usa le stesse
istruzioni, utilizzate per leggere e scrivere in memoria, anche per accedere ai
dispositivi di I/O. I dispositivi di I/O hanno quindi dei propri indirizzi che devono
essere riservati e non sovrapposti a quelli usati per la memoria. I dispositivi di I/O
controllano il bus indirizzi e rispondono solo quando riconoscono un indirizzo a
loro assegnato. Con lI/O-mapped vengono invece usate istruzioni specifiche per
l'esecuzione dell'input/output. I dispositivi di I/O hanno uno spazio indirizzi
separato da quello della memoria, e un segnale del control bus serve alla UC per
specificare se si tratta di un accesso alla memoria o ad un dispositivo periferico. Il
vantaggio dell'uso del memory-mapped che, non richiedendo da una parte
hardware aggiuntivo per la gestione della periferia e dallaltra un insieme di
Il modello di esecutore 73

istruzioni specifiche, consente la realizzazione di CPU con una complessit


inferiore, pi economiche, veloci e facili da costruire.
Il repertorio delle istruzioni ha indirizzato i costruttori di microprocessori
verso due distinte tecnologie: CISC (Complex Instruction Set Computer) e RISC
(Reduced Instruction Set Computer). I processori CISC sono quelli nei quali il
crescere delle potenzialit stato accompagnato da un aumento delle operazioni
che sono capaci di svolgere, inserendo nel linguaggio macchina istruzioni con
potenza espressiva prossima a quella dei linguaggi di programmazione di alto
livello. I processori CISC sono caratterizzati quindi da un ampio repertorio di
istruzioni anche se molte di esse non risultano strettamente necessarie, potendosi
ottenere con lesecuzione di sequenze di istruzioni pi semplici. Il gran numero di
istruzioni di cui sono dotati i processori CISC ha comportato una loro maggiore
complessit costruttiva. La caratteristica di un microprocessore RISC quella di
possedere un repertorio costituito da un ridotto ed essenziale insieme di istruzioni
al fine di ottenere processori pi veloci e di costo ridotto, data la minore
complessit del loro progetto. Le due tecnologie hanno implicazioni diverse a
seconda della tipologia di programmazione adottata. Nel caso di programmazione
direttamente in linguaggio macchina, il ricco repertorio di istruzioni del CISC
rende lo sviluppo di programmi pi semplice. La maggiore complessit dello
sviluppo dei programmi nell'architettura RISC legata alla necessit di dovere
realizzare con istruzioni semplici e in numero ridotto qualsiasi operazione pi
complessa: per tale motivo, diviene di primaria importanza la ottimizzazione del
codice. Se per si analizza l'utilizzazione pratica di set di istruzioni estese dei
CISC, si trova che statisticamente solo un numero molto ridotto di essi viene utilizzato, e
ci non solo perch il 90% di esse pu essere sintetizzato in un restante sottoinsieme del
10%, ma anche perch l'utilit di disporre di un ampio set di istruzioni diminuito nel
tempo a fronte dei progressi compiuti dalle tecniche di sviluppo dei compilatori. Se infatti
si analizza il codice generato da un compilatore, si constata che solo una parte delle
istruzioni vengono impiegate. L'obiettivo fondamentale dell'approccio RISC , in
definitiva, disporre di tale insieme fondamentale di istruzioni per ridurre al minimo il
numero dei cicli di macchina (clock) necessari per loro esecuzione. Tutte le istruzioni
RISC fondamentali hanno la stessa durata (un ciclo macchina), la stessa lunghezza e lo
stesso formato. In questa accezione il RISC rappresenta un nuovo livello di
ottimizzazione tra hardware e software, in cui il primo viene semplificato al massimo per
raggiungere la massima velocit operativa, mentre il secondo si assume l'onere di
compensare la rigidit introdotta nell'hardware.
Relativamente alla tipologia di istruzioni esiste un ulteriore elemento caratterizzante
i microprocessori e consistente nel numero di operandi espliciti presenti nellistruzione.
Si possono avere pertanto microprocessori il cui linguaggio macchina gestisce:
- un solo operando;
- due operandi;
- tre operandi.
Si noti che se da un lato il maggior numero di operandi semplifica lattivit di
programmazione offrendo istruzioni pi compatte, dallaltra rende anche il
processore pi complesso.
74 Capitolo secondo

2.7. Un modello di processore


Da unanalisi dei processori esistenti, si pu ricavare per fini didattici un modello
capace di farne comprendere non solo il funzionamento. ma anche le modalit che
ne permettono la programmazione in linguaggio macchina. Tale processore verr
da qui di seguito indicato con MP, o Modello di Processore. Per semplificare la
presentazione dei concetti di base, al posto del linguaggio macchina in binario o in
formato pi compatto esadecimale, si introduce il linguaggio assemblativo.
In un linguaggio assemblativo si mantiene la corrispondenza uno ad uno con il
linguaggio macchina, ma al codice operativo si associa un codice mnemonico pi
semplice da comprendere e ricordare, ed al posto di indirizzi e valori da riportare in
binario si introducono i valori nella loro rappresentazione esterna (i numeri in
decimale, i caratteri nel formato ASCII). Ad ogni istruzione si pu affiancare
unetichetta per far riferimento ad essa in altre istruzioni, e si pu evidenziare nella
istruzione la tecnica di indirizzamento scelta.
Lassemblatore un programma che esegue la traduzione di un programma,
scritto in linguaggio assemblativo, in linguaggio macchina ed il pi semplice
traduttore di linguaggi presente in informatica. Non deve far altro che:
- far corrispondere ai codici mnemonici del codice operativo il rispettivo
codice binario;
- convertire da decimale a binario indirizzi e valori dei dati;
- determinare gli indirizzi delle etichette associate alle istruzioni;
- convertire dati alfanumerici nella loro rappresentazione binaria.
Per la semplicit del linguaggio assemblativo, la struttura di una istruzione
rigida si pu vedere composta delle seguenti parti:

Label Codice Tecnica Operando Commento


Op. Indirizzamento

La label non sempre presente cos come il commento a fine frase che serve a
spiegare le ragioni per le quali viene introdotta listruzione nel programma. Il
campo Tecnica Indirizzamento indica la modalit di composizione dellindirizzo;
pi precisamente valgono le corrispondenze in tabella 3.
Notazione Tecnica indirizzamento
= Immediata
^ Diretta
(^) Indiretta
(X) Relativa
Tabella 3 Modalit di indirizzamento e notazioni

Non tutte le istruzioni prevedono un campo operando: ovvio che se


loperando non previsto, anche la notazione relativa alla tecnica di
indirizzamento risulta essere assente.
Il processore MP ha allora le seguenti caratteristiche:
- dotato dei registri interni PI, IR, SP, X, ACC e CC;
- I/O mapped;
- di tipo CISC;
Il modello di esecutore 75

- gestisce la rappresentazione per complemento alla base considerando il bit di


peso maggiore dellACC come indicatore del segno (1 per numeri negativi e
zero in caso contrario);
- ha una struttura delle istruzioni ad un operando;
ed a sua volta parte di una architettura composta da:
- una memoria centrale di grandi dimensioni i cui registri di memoria sono
contenitori di informazioni di qualsiasi tipo e dimensione;
- un canale di input (detto standard input) per gestire limmissione dati da
tastiera;
- un canale di output (detto standard output) per la gestione della presentazione
dei risultati su di un monitor.
-

Figura 28 Schema del processore MP

La caratteristica dei registri di memoria di essere contenitori di informazioni


di qualsiasi tipo e dimensione viene introdotta per esemplificare il funzionamento
del modello, senza peraltro dover descrivere in dettaglio le operazioni sui diversi
dati in funzione della specificit della loro rappresentazione. Si potr sempre
dimostrare che un registro di dimensione non specificata si riconduce ad una
sequenza di registri posti ad indirizzi consecutivi contenenti tutti gli elementi posti
nellunico contenitore.
Se ad esempio nel nostro modello si assume che un registro possa contenere il
messaggio Errore, in un sistema reale fatto di una memoria organizzata a byte
serviranno 6 registri contenenti ciascuno le lettere del messaggio. Anche
laccumulatore, per la stessa motivazione, sar considerato un contenitore di
informazioni di qualsiasi tipo e dimensione. Coerentemente con tale ipotesi si potr
assegnare ad un registro un valore numerico o una informazione alfanumerica: nel
primo caso un $ davanti al numero ne indica una rappresentazione esadecimale
($F0AC); nel secondo caso le virgolette racchiuderanno la sequenza di caratteri
(questo un esempio).
Lipotesi semplificativa fatta impatta anche sulla allocazione delle istruzioni in
quanto consente di assumere che una intera istruzione venga ad essere contenuta in un
unico registro. Se una siffatta impostazione ha riguardato i primi calcolatori dotati di una
struttura della memoria detta a voce, nelle architetture moderne organizzate a byte le
istruzioni hanno una lunghezza variabile. Solitamente il repertorio delle istruzioni riesce
ad essere gestito con un solo byte mentre loperando, se presente pu occupare pi byte a
seconda del valore espresso o della capacit di memoria nel caso si tratti di un indirizzo.
Nel caso reale lUC riconosce dal codice operativo dellistruzione anche la sua lunghezza
76 Capitolo secondo

e ne opera il prelievo ad indirizzi consecutivi a quello espresso dal PI: in tale caso, alla
fine della fase fetch, il PI viene pertanto ad essere incrementato di un valore uguale
proprio alla lunghezza dellistruzione. Il repertorio di istruzioni di MP si compone di:
- istruzione di lettura e modifica dei registri interni e di memoria;
- istruzioni di tipo aritmetico;
- istruzioni di tipo logico;
- istruzioni di salto;
- istruzioni di input ed output per la gestione della interazione con il mondo
esterno.
Nel primo sottoinsieme rientrano tutte le istruzioni di gestione dei registri di
memoria con una delle tecniche di indirizzamento introdotte, dei registri interni
ACC, X, CC e SP. Le istruzioni di tipo aritmetico e logico depositano il loro
risultano nellaccumulatore modificando i bit del CC di segno, riporto, zero ed
overflow. Le istruzioni di salto alterano il normale flusso di controllo del
programma prescrivendo che la esecuzione non proceda con listruzione successiva
il cui indirizzo specificato in PI, ma con quella indicata dal valore delloperando
secondo modalit diverse.
Per ogni istruzione verr riportato il codice operativo (CodOP) in linguaggio
assemblativo, loperando se previsto, la operazione eseguita, una breve descrizione
e le eventuali modifiche apportate ai bit del CC (0 o 1 a seconda dei casi, o A ad
indicare la dipendenza dal valore assunto dallaccumulatore). Con la notazione
[registro] si indicher il contenuto del registro specificato (ad esempio [X] indica il
contenuto del registro indice), mentre con M([registro]) si indicher il registro di
memoria il cui indirizzo specificato in registro. La presenza nel campo operando
di OP indica che listruzione richiede che venga determinato un valore sulla base di
una delle tecniche di indirizzamento specificate:
- proprio OP per limmediato,
- M(OP) per il diretto,
- M([M(OP)]) per lindiretto
- M([X]) per il relativo.
Per semplificare si indicher con [OP] il valore determinato.

Le istruzioni di gestione dellaccumulatore sono quelle riportate in tabella 4.


CodOP Operando Operazione Descrizione
LDA OP ACC = [OP] copia in ACC il dato come determinato dalla
tecnica di indirizzamento
STA OP [OP] = ACC copia nel registro di memoria, il cui indirizzato
determinato dalla tecnica di indirizzamento, il
valore di ACC
PSHA M([SP]) = ACC prima copia ACC nellarea stack di memoria,
SP = SP - 1 ossia allindirizzo contenuto in SP; dopo viene
decrementato il valore di SP per consentire un
successivo inserimento nellarea
PULA SP = SP + 1 dopo aver incrementato il contenuto del registro
ACC = M([SP]) SP, copia in ACC il contenuto del registro di
memoria indicato da SP
CLRA ACC = 0 azzera il contenuto di ACC
NEGA ACC=0 - [ACC] calcola il complemento alla base del contenuto di
ACC
Tabella 4 Istruzioni per la gestione dellaccumulatore
Il modello di esecutore 77

Le istruzioni di gestione del registro X sono quelle in tabella 5.


CodOP Operando Operazione Descrizione
LDX OP X = [OP] copia in X il dato come determinato
dalla tecnica di indirizzamento
STX OP [OP] = X copia nel registro di memoria, il cui
indirizzato determinato dalla tecnica di
indirizzamento, il valore di X
Tabella 5 Istruzioni per la gestione del registro indice

Le istruzioni di gestione del registro SP sono quelle in tabella 6.


CodOP Operando Operazione Descrizione
LDSP OP SP = [OP] copia in SP il dato come determinato
dalla tecnica di indirizzamento
STSP OP [OP] = SP copia nel registro di memoria, il cui
indirizzato determinato dalla tecnica di
indirizzamento, il valore di SP
Tabella 6 Istruzioni per la gestione del registro SP

Le istruzioni di tipo aritmetico sono quelle in tabella 7.


CodOP Operando Operazione Descrizione
INCA ACC = [ACC] +1 modifica il contenuto di ACC
aggiungendo 1
DECA ACC = [ACC] -1 modifica il contenuto di ACC sottraendo
1
INCX X = [X] +1 modifica il contenuto di X aggiungendo
1
DECX X = [X] -1 modifica il contenuto di X sottraendo 1
INCS SP = [SP] +1 modifica il contenuto di SP aggiungendo
1
DECS SP = [SP] -1 modifica il contenuto di X sottraendo 1
ADDA OP ACC = [ACC] + [OP] Somma al contenuto di ACC il valore
determinato sulla base della tecnica di
indirizzamento
SUBA OP ACC = [ACC] - [OP] Sottrae al contenuto di ACC il valore
determinato sulla base della tecnica di
indirizzamento
Tabella 7 Istruzioni aritmetiche

Le istruzioni di tipo logico in tabella 8.


CodOP Operando Operazione Descrizione
NOTA ACC = not[ACC] complemento di ACC; i bit 1 vengono
cambiati in 1 e quelli 0 in 1
ANDA OP ACC = [ACC] and Prodotto logico tra il contenuto di ACC e il
[OP] valore determinato sulla base della tecnica
di indirizzamento; land viene eseguito sui
singoli bit coinvolgendo quelli che
occupano la stessa posizione
ORA OP ACC = [ACC] or [OP] Somma logica tra il contenuto di ACC e il
valore determinato sulla base della tecnica
di indirizzamento; lor viene eseguito sui
singoli bit coinvolgendo quelli che
occupano la stessa posizione
Tabella 8 Istruzioni di tipo logico

Le istruzioni di confronto, utili a modificare i bit del CC, sono in tabella 9.


78 Capitolo secondo

CodOP Operando Operazione Descrizione


BITA OP [ACC] and [OP] Se Z diverso da zero, indica che ACC
ha i bit uguali a 1 nelle stesse posizioni
di OP
CMPA OP [ACC] - [OP] Se Z uguale a zero ACC OP hanno la
stessa configurazione di bit
TST OP [OP] 0 Se OP uguale a zero Z uguale ad 1
Se OP negativo S uguale ad 1
TSTA [ACC] 0 Se ACC uguale a zero Z uguale ad 1
Se ACC negativo S uguale ad 1
Tabella 9 Istruzioni di confronto

Le istruzioni di salto sono in tabella 10.


CodOP Operando Operazione Descrizione
JMP Address PI = Address Salto allistruzione presente allindirizzo
specificato
BRZ Address Z=1 PI = Address Se il bit Z 1 salta allistruzione
allindirizzo specificato altrimenti
prosegui in sequenza
BRNZ Address Z=0 PI = Address Se il bit Z 0 salta allistruzione
allindirizzo specificato altrimenti
prosegui in sequenza
BRS Address S=1 PI = Address Se il bit S 1 salta allistruzione
allindirizzo specificato altrimenti
prosegui in sequenza
BRNS Address S=0 PI = Address Se il bit S 0 salta allistruzione
allindirizzo specificato altrimenti
prosegui in sequenza
BRO Address O=1 PI = Address Se il bit O 1 salta allistruzione
allindirizzo specificato altrimenti
prosegui in sequenza
BRNO Address O=0 PI = Address Se il bit O 0 salta allistruzione
allindirizzo specificato altrimenti
prosegui in sequenza
BRC Address C=1 PI = Address Se il bit C 1 salta allistruzione
allindirizzo specificato altrimenti
prosegui in sequenza
BRNC Address C=0 PI = Address Se il bit C 0 salta allistruzione
allindirizzo specificato altrimenti
prosegui in sequenza
Tabella 10 Istruzioni di salto

Mentre il JMP (jump) un salto incondizionato, tutti i BRANCH sono salti


condizionati al valore del bit del CC interessato dallistruzione. Se la condizione
non viene verificata, il PI non viene modificato e lesecuzione continua con
listruzione successiva a quella di salto.
Le istruzioni di gestione dei sottoprogrammi sono in tabella 11.
CodOP Operando Operazione Descrizione
JSR Address M([SP]) = PI (Jump to subroutine)
SP = [SP] - 1 Salto allistruzione indicata da address
dopo aver salvato il valore di PI
PI = Address nellarea stack
RTS SP = [SP] + 1 (Return from subroutine)
PI = M([SP]) Salta allistruzione il cui indirizzo viene
prelevato dallarea stack
Tabella 11 Istruzioni di gestione sottprogrammi
Il modello di esecutore 79

Le istruzioni di gestione del CC sono in tabella 12.


CodOP Operando Operazione Descrizione
CLS S=0 pone a zero il bit S
SETS S=1 pone a uno il bit S
CLZ Z=0 pone a zero il bit Z
SETZ Z=1 pone a uno il bit Z
CLO O=0 pone a zero il bit O
SETO O=1 pone a uno il bit O
CLC C=0 pone a zero il bit C
SETC C=1 pone a uno il bit C
CLI I=0 pone a zero il bit I
SETI I=1 pone a uno il bit I
Tabella 12 Istruzioni di gestione del registro CC

Infine le istruzioni di input ed output sono quelle in tabella 13.


CodOP Operando Operazione Descrizione
IN ACC = [input Il dato prelevato dallesterno (per
standard] inserimento da tastiera) dal canale di
input viene depositato in ACC
OUT output standard = Il valore di ACC viene fornito al canale
[ACC] di output per essere mostrato allesterno
(sul monitor)
Tabella 13 Istruzioni di I/O
Si pu osservare che non tutte le istruzioni sono realmente necessarie. Infatti
molte di esse possono essere ricavate da altre. Non a caso il processore MP stato
classificato come CISC proprio perch stata privilegiata la presenza di un ricco
numero di istruzioni al fine di renderne pi semplice la programmazione.
In tabella 14 sono riportati alcuni esempi di ridondanza del repertorio
introdotto.
Codice Operativo Equivalenza
INCA STA (^)2000
LDA (=)1
ADDA (^)2000
CLRA LDA (=)0
NEGA NOTA
INCA
Tabella 14 Istruzioni Equivalenti

2.7.1. La programmazione in linguaggio assemblativo


La programmazione nel linguaggio assemblativo resa difficile non solo dalla
bassa potenza espressiva delle istruzioni che devono essere utilizzate, ma anche
dallobbligo che ha il programmatore di pianificare la disposizione in memoria di
dati ed istruzioni. Per ogni programma si deve provvedere a suddividere la
memoria in tre aree fondamentali:
- unarea istruzioni necessaria per contenere lintero programma;
- unarea dati per gestire i valori delle informazioni da trattare;
- unarea stack importante per gestire sottoprogrammi e interruzioni.
80 Capitolo secondo

Le tre aree devono essere adeguatamente dimensionate affinch non capiti che durante
lesecuzione del programma una di esse sconfini nelle altre. Se ci accadesse si avrebbero
effetti non prevedibili e di difficile diagnosi. Ad esempio, se durante lesecuzione alcuni dati
venissero memorizzati nellarea riservata alle istruzioni, accadrebbe che la UC incontrerebbe
istruzioni diverse da quelle inserite dal programmatore. Altra causa di errore pu generarsi per
effetto di una espansione non controllata dellarea di stack a scapito dellarea dati: la
sovrapposizione cambia il valore dei registri di memoria riservate a contenere i valori delle
informazioni alterando quanto previsto dal programmatore. Se ad esempio in un registro di
memoria stato inserito un valore costante da cui dipende la correttezza dellalgoritmo, e tale
registro viene ad essere modificato per effetto della espansione dellarea stack, si ha come
naturale conseguenza la generazione di una condizione di errore.
La memoria viene pertanto suddivisa ponendo:
- larea istruzioni ad indirizzi bassi in quanto lallocazione delle istruzioni
del programma deve essere fatta ad indirizzi consecutivi crescenti;
- larea stack ad indirizzi alti in quanto le operazioni di inserimento
avvengono decrementando lo SP
- larea dati ad indirizzi intermedi tra le due precedenti aree.
Supponendo quindi di disporre di una memoria con una capacit di 1000
registri, tutti i programmi dovrebbero almeno prevedere come prima istruzione
quella di caricamento di SP con 999. Per comodit si assumer anche che la prima
istruzione sia collocata sempre nel registro di memoria di indirizzo uno. Con tale
ipotesi perch il processore MP inizi lesecuzione di un qualsiasi programma
baster forzare il PI ad azzerarsi in fase di boot.
A seconda delle necessit, il programmatore dovr disporre i dati in indirizzi
compresi tra 1 e 999 dopo aver dimensionato lo spazio necessario a contenere le
istruzioni del programma e larea stack.
Un primo e semplice programma per il nostro MP quello in tabella 15.

IM Label CodOp TI Operando Commento


1 LDSP = 999 posizionamento iniziale di SP
2 LDA = Inserire carica una stringa in accumulatore
dato
3 OUT visualizza messaggio
4 IN input di un dato da tastiera
5 STA ^ 500 conserva primo dato
6 LDA = Inserire carica una stringa in accumulatore
dato
7 OUT visualizza messaggio
8 IN input di un secondo dato da tastiera
9 ADDA ^ 500 sommalo al primo dato prelevato da
tastiera
10 OUT presenta il risultato al monitor
Tabella 15 Esempio di programma per MP

Per comodit stata inserita la colonna IM (indirizzo di memoria) per


indicare lallocazione in memoria delle singole istruzioni. Per comprendere cosa il
programma faccia se ne pu tracciare la esecuzione elencando in successione non
solo le azioni svolte, ma anche i suoi effetti sui registri interni e di memoria.
Il modello di esecutore 81

Allo scopo viene inserita la tabella di trace (vedi tabella 16) che comprende
tante colonne quanti sono i registri da osservare e tante righe quante sono le azioni
svolte. Ogni riga riporta la fotografia dello stato in cui viene a trovarsi il sistema:
linsieme degli stati nellordine temporale di esecuzione rappresenta il processo
svolto. Nella tabella di trace si aggiungono due colonne per elencare le interazioni
del sistema con il mondo circostante: infatti i processi svolti dipendono fortemente
da tale interazioni e sono casi particolari quelli nei quali ad un programma
corrisponde un unico processo di esecuzione qualsiasi siano i dati immessi. Le due
colonne aggiunte sono pertanto:
- il file di output (FO) nel quale si elenca tutto ci che viene presentato
sulloutput standard per effetto di operazioni d OUT;
- il file di input (FI) dove sono elencati i dati di prova che possono essere
inseriti dallinput standard per effetto di operazioni di IN.

Nel caso in cui si fissi che i programmi siano caricati a partire dallindirizzo 1,
larea dati inizi allindirizzo 500, larea stack parta da 999, si pu strutturare la
tabella di trace del programma precedente nel modo seguente riportando lo stato
del sistema al termine della fase execute dellUC.

PI IR ACC SZOC X SP M(500) M(501) M(502) M(503) M(997) M(998) M(999) FO FI


2 LDSP 999
=999
3 LDA Inserire 0000
=Inseri dato
re dato
4 OUT Inserire
dato
5 IN 33 33
6 STA 33
^500
7 LDA Inserire
=Inseri dato
re dato
8 OUT Inserire
dato
9 IN 22 22
10 ADDA 55
^500
11 OUT 55
Tabella 16 Tabella di trace

Si noti che per comodit di lettura nelle celle vengono riportati solo i
cambiamenti sottintendendo che lultimo valore scritto quello che rimane nel
registro fin quando non interviene una successiva modifica.
Il trace mostra che il programma esegue la somma di due numeri. Non si
comprende per cosa accade dopo lultima istruzione di OUT soprattutto
considerando che il ciclo del processore non ha mai termine e dopo la fase execute
deve sempre occorrere una successiva fase di fetch. Per gestire tale situazione tutti
i programmi che verranno presentati successivamente prevederanno come ultima
listruzione un JMP sis_op che verr illustrata solo alla fine del paragrafo.
82 Capitolo secondo

Il programma seguente esegue lo scambio tra due registri di memoria nel


senso che porta nel primo il valore del secondo e nel secondo quello del primo: per
evitare che nello scambio uno dei due valori si perda deve essere usato un terzo
registro che fa da buffer. Per semplificare la progettazione il programmatore pu
associare un nome ai registri di memoria usati per conservare dati. Ad esempio
creata la seguente corrispondenza:

Nome Indirizzo di memoria


info01 500
info02 501
buffer 503
Tabella 17 Corrispondenza nomi-indirizzi di memoria

si potranno usare i nomi al posto degli indirizzi rendendo il programma pi


comprensibile. Un altro esempio di programma quello in tabella 18.

IM Label CodOp TI Operando Commento


1 LDSP = 999 posizionamento iniziale di SP
2 LDA = Dato carica una stringa in
1> accumulatore
3 OUT visualizza messaggio
4 IN input di un dato da tastiera
5 STA ^ info01 conserva primo dato
6 LDA = Dato carica una stringa in
2> accumulatore
7 OUT visualizza messaggio
8 IN input di un secondo dato da
tastiera
9 STA ^ info02 conserva secondo dato
10 scambio LDA ^ info1 inizia scambio
11 STA ^ buffer conserva il primo valore
12 LDA ^ info2 preleva il secondo valore
13 STA ^ info01 copialo nel primo registro
14 LDA ^ buffer riprendi il primo valore
15 STA ^ info02 portalo nel secondo registro
16 Stampa LDA = Dato1: carica messaggio
17 OUT visualizza messaggio
18 LDA ^ info01
19 OUT visualizza valore
20 LDA = Dato2: carica messaggio
21 OUT visualizza messaggio
22 LDA ^ info02
23 OUT visualizza valore
24 Fine JMP sis_op salta allindirizzo sis_op
Tabella 18 Esempio di programma per MP
Il modello di esecutore 83

Il trace del programma quello riportato nella tabella 19.


PI IR ACC SZO X SP info info buffer M(997) M(998) M(999) FO FI
C 01 02
2 LDSP 99
=999 9
3 LDA Dato 0000
= 1>
Dato1>
4 OUT Dato
1>
5 IN 33 33
6 STA 33
^info01
7 LDA Dato
= 2>
Dato 2>
8 OUT Dato
2>
9 IN 22 22
10 STA 22
^info02
11 LDA 33
^info01
12 STA 33
^buffer
13 LDA 22
^info02
14 STA 22
^info01
15 LDA 33
^buffer
16 STA 33
^info02
17 LDA Dato 1:
=Dato 1:
18 OUT Dato
1:
19 LDA 22
^info01
20 OUT 22
21 LDA Dato 2:
=Dato 2:
22 OUT Dato
2:
23 LDA 33
^info02
24 OUT 33
Tabella 19 Tabella di trace

Per evitare di dover ripetere sequenze identiche di istruzioni si possono


introdurre i sottoprogrammi con le istruzioni JSR e RTS. Infatti la JSR esegue un
salto allindirizzo specificato dopo aver conservato nellarea di memoria gestita
dallo stack il valore assunto dal PI: tale valore indica la posizione in memoria della
istruzione successiva al JSR. Il salto fa procedere lesecuzione con le istruzioni che
compongono il sottoprogramma. Listruzione RTS, posta al termine del
sottprogramma, ripristina nel PI il valore che era stato conservato allatto del JSR
riprendendo in tale modo lesecuzione del programma chiamante.
84 Capitolo secondo

Nella figura seguente si organizzato come sottoprogramma la sequenza


formata dalla stampa il valore dellaccumulatore e dallinput di un nuovo dato da
tastiera.

Figura 29 Gestione Sottoprogrammi

Il trace in tabella 20 seguente mostra in dettaglio come avviene sia il salto al


sottoprogramma che il ritorno da esso.

PI IR ACC SZOC X SP dato1 dato2 M(997) M(998) M(999) FO FI


2 LDSP 99
=999 9

101 LDA Dato 0000


= Dato 1>
1>
102 JSR 300 99 102
8
301 OUT Dato
1>
302 IN 33 33
102 RTS 99
9
103 STA 33
^dato1
104 LDA Dato
= Dato 2>
2>
105 JSR 300 99 105
8
301 OUT Dato
1>
302 IN 33 33
Il modello di esecutore 85

102 RTS 99
9
10 STA 22
^dato2
Tabella 20 Tabella di trace

Lintroduzione dei sottoprogrammi obbliga il programmatore a pianificarne


lallocazione in memoria in area diversa da quella occupata dal programma
principale. Per semplificare il lavoro del programmatore si potr associare un nome
allindirizzo occupato dalla prima istruzione del sottoprogramma cos come stato
gi fatto per i dati. I programmi prodotti saranno molto pi leggibili.
Si scriva adesso un programma che:
- carichi in memoria una sequenza di dati numerici di tipo intero, maggiori
di zero prelevandoli da tastiera;
- termini il prelievo quando viene inserito un valore nullo;
- effettui la somma dei dati inseriti;
- determini il valore minimo tra quelli inseriti;
- determini il valore massimo tra quelli inseriti.
Per prima cosa si organizzino i sottoprogrammi per:
- LEGGI: per la stampa di un messaggio, linserimento da tastiera e il
salvataggio in memoria;
- CONTA: per incrementare una variabile contatore;
- IN_VET: per leggere la sequenza dei numeri determinando quanti sono;
- SOMMA: per effettuare la sommatoria dei dati inseriti;
- MAX: per determinare il valore massimo e
- MIN: per determinare il valore minimo degli stessi dati.
Se si definiscono i nomi per le variabili e per le etichette di istruzioni come in
tabella 21 (tabella dei simboli),

Simbolo Indirizzo Memoria Tipo Descrizione


Vettore 600 variabile inizio sequenza dati
(si assume che possano
essere massimo 100)
n 700 variabile numero dati inseriti
i 701 variabile indice di ciclo
totale 702 variabile valore somma
max 703 variabile valore massimo
min 704 variabile valore mnimo
leggi 100 label sottoprogramma
conta 110 label sottoprogramma
in_vet 120 label sottoprogramma
somma 150 label sottoprogramma
minimo 200 label sottoprogramma
massimo 250 label sottoprogramma
Tabella 21 Tabella dei simboli

si pu procedere con la programmazione.


86 Capitolo secondo

IM Label CodOp TI Operando Commento


100 LEGGI OUT stampa messaggio
101 IN leggi dato
102 STA (X) memorizza dato
103 RTS termina
Tabella 21 Programma LEGGI

Si noti che LEGGI prevede prima di iniziare che :


- in ACC sia contenuto il messaggio da stampare
- in X sia contenuto lindirizzo di memoria dove conservare il dato preso da
tastiera.
Al suo termine il dato inserito da tastiera rimane in ACC e si trova anche
allindirizzo di memoria contenuto in X.
IM Label CodOp TI Operando Commento
110 CONTA LDA ^ n carica valore contatore
111 INCA incrementa
112 STA ^ n aggiorna contatore
113 RTS termina

Tabella 22 Programma CONTA

Si noti che CONTA preleva ed aggiorna il valore di n e non ha bisogno di


scambiare dati attraverso gli atri registri.
IM Label Cod Op TI Operando Commento
120 IN_VET LDX = vettore carica in X indirizzo di
vettore
121 CLRA azzera contatore
122 STA ^ n
123 CICLO LDA = Inserisci carica messaggio
124 JSR LEGGI Salta a sottoprogramma
125 BRZ FINE_CICLO salto condizionato alla
presenzadi uno zero in ACC
(terminazione sequenza di
input)
126 INCX se il salto non avviene il
dato inserito diverso da
zero e si deve procedere al
successivo inserimento in
una posizione di memoria
seguente
127 JSR CONTA incrementa contatore
128 JMP CICLO riprendi linserimento
129 FINE_CICLO RTS termina
Tabella 23 Programma IN_VET
Si noti che IN_VET restituisce il valore di n e le n locazioni di memoria che
partono da vettore caricate con i dati inseriti da tastiera.
Il modello di esecutore 87

IM Label CodOp TI Operando Commento


150 SOMMA LDX = vettore carica in X indirizzo di
vettore
151 CLRA
152 STA ^ tot azzera somma
153 STA ^ i salva valore indice
154 CICLO_SOM LDA (X) preleva dato da sequenza
155 ADDA ^ tot aggiungi la somma
precedente
156 STA ^ tot conserva il valore
calcolato
157 INCX incrementa la posizione
nella sequenza
158 LDA ^ i carica valore indice
159 INCA incrementa
160 STA ^ i salva valore indice
161 CMPA ^ n confronta con il numero di
elementi
162 BRNZ CICLO_SOM se non sono uguali
continua la somma
163 RTS
Tabella 24 Programma SOMMA
IM Label CodOp TI Operando Commento
200 MINIMO LDX = vettore carica in X indirizzo di vettore
201 LDA (X) preleva primo dato da sequenza
202 STA ^ min posiziona in valore min
203 LDA = 1
204 STA ^ i posiziona ad 1 indice
205 CICLO_MIN INX incrementa la posizione nella
sequenza
206 LDA (X) preleva dato da sequenza
207 SUBA ^ min fai la differenza con min
208 BRNS CONTINUA se la differenza non negativa
lelemento pi grande del
presunto minimo
209 LDA (X) riprendi il dato
210 STA ^ min aggiorna minimo
211 CONTINA LDA ^ i carica valore indice
212 INCA incrementa
213 STA ^ i salva valore indice
214 CMPA ^ n confronta con il numero di
elementi
215 BRNZ CICLO_MIN se non sono uguali continua i
confronti
216 RTS
Tabella 25 Programma MINIMO
IM Label CodOp TI Operando Commento
250 MASSIMO LDX = vettore carica in X indirizzo di vettore
251 LDA (X) preleva primo dato da sequenza
88 Capitolo secondo

252 STA ^ max posiziona in valore max


253 LDA = 1
254 STA ^ i posiziona ad 1 indice
255 CICLO_MAS INX incrementa la posizione nella
sequenza
256 LDA ^ max preleva max
257 SUBA (X) fai la differenza con dato da
sequenza
258 BRNS PROSEGUI se la differenza non negativa il
presunto max pi grande
dellelemento della sequenza
259 LDA (X) riprendi il dato
260 STA ^ max aggiorna massimo
261 PROSEGUI LDA ^ i carica valore indice
262 INCA incrementa
263 STA ^ i salva valore indice
264 CMPA ^ n confronta con il numero di elementi
265 BRNZ CICLO_MAS se non sono uguali continua i
confronti
266 RTS
Tabella 26 Programma MASSIMO

Si pu adesso scrivere il programma principale.


IM Label Cod TI Operando Commento
Op
1 LDSP = 999 posizionamento iniziale di SP
2 JSR IN_VET lettura della sequenza
3 JSR SOMMA calcola la somma degli elementi
4 LDA ^ Totale
5 OUT stampa il risultato della somma
6 JSR MINIMO determina valore minimo
7 LDA ^ Min
8 OUT stampa valore minimo
6 JSR MASSIMO determina valore massimo
7 LDA ^ Max
8 OUT stampa valore massimo
9 JMP sist_op
Tabella 27 Programma MAIN
Si noti che si fatto ricorso ai sottoprogrammi anche e soprattutto per
evidenziare sequenze di istruzioni con un compito preciso. In tale modo dal
programma principale si evincono subito le operazioni fondamentali senza doverle
scoprire attraverso il trace.
Un altro aspetto che emerge dallesempio che i richiami dei sottoprogrammi
possono essere nidificati: ossia allinterno di un sottoprogramma si possono
richiamare altri sottoprogrammi. Il collegamento tra le diverse componenti del
programma garantito dalla gestione dellarea di memoria attraverso il registro SP.
Ad ogni salto al sottoprogramma viene inserito (operazione di PUSH) lindirizzo di
ritorno nellarea stack di memoria. Listruzione RTS preleva (operazione di POP)
Il modello di esecutore 89

lultimo indirizzo inserito a cui tornare. I push conservano gli indirizzi di ritorno
nellordine in cui si sono determinati; i pop li prelevano nellordine inverso.

Figura 30 Gestione sottoprogrammi nidificati

Lassemblatore stato uno dei primi programmi realizzati. Nel tempo si


sempre pi arricchito di funzionalit che semplificassero la difficile attivit di
programmazione. Negli attuali assemblatori il programmatore non ha bisogno di
definire la tabella dei simboli in quanto viene calcolata automaticamente durante la
traduzione in linguaggio macchina. Per farlo lassemblatore opera solitamente in
due passi: nel primo costruisce la tabella dei simboli associando ad ognuno di essi
posizione in memoria e caratteristica, nel secondo pu procedere alla sostituzione:
- dei codici mnemonici con quelli macchina,
- e dei simboli con i valori determinati.

Il programmatore pu anche fornire allassemblatore direttive con le quali


guidare la traduzione. Per esempio pu indicare allassemblatore:
- dove allocare le istruzioni:
#AREAPROG AT address
- dove allocare i dati definendone anche le dimensioni:
#AREADATI AT address
- le dimensioni dei singoli dati con direttive del tipo:
#DATO nome SIZE dimensione
- dove far iniziare larea di stack:
#AREASTACK AT address
- dove collocata la prima istruzione da eseguire che pu essere diversa da
quella allocata in prima posizione:
#START AT address
- quando termina lesecuzione del programma
#STOP
90 Capitolo secondo

Un tale assemblatore consentirebbe di scrivere i programmi precedenti senza


dover segnare a fianco di istruzioni e dati la loro allocazione in memoria come
lesempio seguente mostra.

#START AT 200
#AREADATI AT 500
#DATO vettore SIZE 100
#DATO n SIZE 1
#DATO i SIZE 1
#DATO totale SIZE 1
#DATO max SIZE 1
#DATO min SIZE 1
#AREASTACK AT 999
#AREAPROG AT 200

JSR IN_VET
JSR SOMMA
LDA ^ totale
OUT
JSR MINIMO
LDA ^ min
OUT
JSR MASSIMO
LDA ^ max
OUT
#STOP

AREAPROG AT 300

LEGGI OUT
IN
STA (X)
RTS

CONTA LDA ^ n
INCA
STA ^ n
RTS

IN_VET LDX = vettore


CLRA
STA ^ n
CICLO LDA = Inserisci
JSR LEGGI
BRZ END_CICLO
INCX
JSR CONTA
JMP CICLO
END_CICLO RTS

SOMMA LDX = vettore


CLRA
STA ^ tot
STA ^ i
CICLO_SOM LDA (X)
ADDA ^ tot
Il modello di esecutore 91

STA ^ tot
INCX
LDA ^ i
INCA
STA ^ i
CMPA ^ n
BRNZ CICLO_SOM
RTS

MINIMO LDX = vettore


LDA (X)
STA ^ min
LDA = 1
STA ^ i
CICLO_MIN INX
LDA (X)
SUBA ^ min
BRNS CONTINUA
LDA (X)
STA ^ min
CONTINUA LDA ^ i
INCA
STA ^ i
CMPA ^ n
BRNZ CICLO_MIN
RTS

MASSIMO LDX = vettore


LDA (X)
STA ^ max
LDA = 1
STA ^ i
CICLO_MAS INX
LDA ^ max
SUBA (X)
BRNS PROSEGUI
LDA (X)
STA ^ max
PROSEGUI LDA ^ i
INCA
STA ^ i
CMPA ^ n
BRNZ CICLO_MAS
RTS

Lassemblatore inserir in automatico le istruzioni:

LDS = address_area_stack
JMP start_address

allinizio del programma, e:

JMP sistema_operativo
al posto della direttiva #STOP.
92 Capitolo secondo

Lultima direttiva utile per comprendere due aspetti della architettura che
sono stati fino a questo momento trascurati:
- come inserire un programma in memoria prima che il processore MP
possa eseguirlo;
- cosa accade quanto tale programma termina coerentemente con lipotesi
fatta sul ciclo del processore che prevede che dopo una fase execute ci sia
sempre una fase fetch successiva.
Per rispondere alla prima domanda si potrebbe far riferimento ai primi sistemi
di calcolo che erano dotati di un lettore di nastro di carta perforato e di due pulsanti
di LOAD e di RUN. Il programmatore era costretto a scrivere il proprio
programma su di un nastro di carta con un sistema perforatore che faceva un buco
ogni volta che un bit era uguale ad uno. Scritto il nastro lo caricava con il pulsante
di LOAD nella memoria centrale del sistema di calcolo e solo al termine del
travaso poteva avviare la CPU con il comando di RUN. Per eseguire un altro
programma si doveva ripetere la procedura di caricamento e di avvio. La modifica
ad un programma costringeva il programmatore alla riscrittura dellintero nastro di
carta. La CPU restava a lungo inutilizzata in quanto iniziava lesecuzione solo
quando il pulsante di RUN veniva premuto dalloperatore di sistema.
E anche larchitettura del processore MP potrebbe funzionare nello stesso
modo. Si pu per costruire un programma che affidi proprio a MP lonere di
effettuare il caricamento in memoria delle istruzioni di un altro programma
prelevandole dallinput standard: a caricamento avvenuto la MP pu procedere alla
sua esecuzione per poi ritornare al caricamento di un successivo programma. Si
chiami LOADER il programma di caricamento e UTENTE il programma caricato. In
ogni istante il sistema si trova o ad eseguire il LOADER o lUTENTE.

Figura 31: MP e caricamento programmi

Ad esempio il LOADER per larchitettura MP caratterizzato da:


- occupazione delle prime posizioni di memoria sia per area codice che
dati;
- prima istruzione da eseguire collocata allindirizzo uno;
- caricamento delle programma UTENTE fissata con inizio a partire
dallindirizzo 100;
Il modello di esecutore 93

- riconoscimento di MEM come posizione a partire dalla quale iniziare


il caricamento; per semplicit si assume che lindirizzo di
caricamento venga inserito successivamente a MEM;
- riconoscimento di END come terminazione del caricamento.

Si noti che MEM ed END sono dichiarazioni che vengono introdotte per
guidare il LOADER e non corrispondono quindi a istruzioni che dovranno essere
eseguite da MP. comunque lassemblatore ad introdurle nel programma tradotto
in linguaggio macchina. Il codice del LOADER potrebbe essere allora il seguente.

Caricando le istruzioni a partire dalla prima posizione, la label SIST_OP


assume il valore 10 e tutto il codice del LOADER occupa solo 28 registri di
memoria. Per comprendere il programma di LOADER si effettui il trace nellipotesi
che il programma da caricare sia quello di somma di due numeri. Si supponga che
tale programma sia prodotto dallassemblatore che ha inserito le direttive MEM e
END. Inoltre, per rendere pi leggibile lesempio si sono mantenute le istruzioni in
linguaggio assemblativo invece di riportarne il codice binario prodotto
dallassemblatore.
PI IR ACC SZOC X SP M(80) M(81) M(82) M(83) M(84) M(85) M(100) M(101) M(200) FO FI
2 LDSP 99
=99
3 LDA Prompt> 0000
=Prompt>
4 STA ^80 Prompt>

5 LDA =100 100

6 STA ^81 100


94 Capitolo secondo

7 LDA MEM
=MEM
8 STA ^82 MEM

9 LDA END
=END
10 STA ^83 END

11 LDX ^81 100

12 LDA ^80 Prompt>

13 OUT Prompt>

14 IN LDSP LDSP^999
^999
15 STA ^84 LDSP
^999
16 CMPA ^83 1000

17 BRZ 28

18 LDA ^84 LDSP 0000


^999
19 CMPA ^82 1000

20 BRNZ 24

25 LDA ^84 LDSP 0000


^999
26 STA (X) LDSP
^999
27 INCX 101

28 JMP 11

12 LDA ^80 Prompt>

13 OUT Prompt>

14 IN LDSP JMP100
^999
15 STA ^84 LDSP
^999
16 CMPA ^83 1000

17 BRZ 28

18 LDA ^84 JMP100 0000

19 CMPA ^82 1000

20 BRNZ 24

25 LDA ^84 JMP100 0000

26 STA (X) JMP


100
27 INCX 102

28 JMP 11

12 LDA ^80 Prompt>

13 OUT Prompt>

14 IN MEM MEM

15 STA ^84 MEM

16 CMPA ^83 1000

17 BRZ 28

18 LDA ^84 MEM 0000

19 CMPA ^82 1100


Il modello di esecutore 95

20 BRNZ 24

21 IN 200 0000 200

22 STA ^85 MEM

23 LDX ^85 200 200

24 JMP 11

12 LDA ^80 Prompt>

13 OUT Prompt>

14 IN IN IN

15 STA ^84 IN

16 CMPA ^83 1000

17 BRZ 28

18 LDA ^84 IN 0000

19 CMPA ^82 1000

20 BRNZ 24

25 LDA ^84 IN 0000

26 STA (X) IN

27 INCX 201

28 JMP 11

12 LDA ^80 Prompt>

13 OUT Prompt>

14 IN END END

15 STA ^84 IN

16 CMPA ^83 1100

17 BRZ 28

29 JMP 100

101 LDSP =999

102 JMP 200

201 IN

206 JMP 10

11 LDX ^81 100

12 LDA ^80 Prompt>

13 OUT Prompt>

Tabella 27 Tabella di Trace del LOADER

Nel trace sono state saltate alcune righe perch identiche alle precedenti se
non nei dati inseriti e nei valori di X, aventi valori via via crescenti. Cos non
96 Capitolo secondo

stato condotto il trace del programma UTENTE mostrando solo la sua


terminazione con il salto al LOADER che fa riprendere il caricamento di un
successivo programma in memoria.

Il LOADER stato il primo componente di un insieme di programmi che


hanno consentito al sistema stesso di adempiere a tutta la sua gestione. Tali
programmi sono detti Sistema Operativo. Se le istruzioni del LOADER sono
conservate in una ROM e allatto dellaccensione del sistema si forza il PI ad
assumere il valore zero con circuiteria apposita, vengono ad essere soddisfatte le
condizioni previste dalla fase di boot e necessarie affinch il ciclo del processore
MP possa avere inizio.

Da ultimo si vuole rimuovere lipotesi introdotta riguardante la strutturazione


dei registri in contenitori di informazioni di qualsiasi tipo. Lesempio che segue
confronta una istruzione di MP con un caso reale di processore che opera con
registri di memoria a byte. Nellesempio si fatto ricorso ai codici ASCII dei
caratteri che compongono il messaggio.

Processore MP Processore a Byte


LDA =CIAO LDA =C
OUT OUT
LDA =I
OUT
LDA =A
OUT
LDA =O
OUT

Si comprende dallesempio che lipotesi stata introdotta solo per la


semplificazione degli esempi.
Capitolo terzo

Algoritmi e programmi

3.1. Informatica come studio di algoritmi


Aver definito linformatica come la scienza e la tecnica dellelaborazione dei
dati o, pi in generale, del trattamento automatico delle informazioni, porta
inevitabilmente a riflettere sul fatto che per elaborare linformazione sia necessario
preliminarmente comprendere il modo in cui procedere nella sua elaborazione.
In un tale contesto linformatica allarga i suoi orizzonti diventando lo studio
sistematico dei processi che servono al trattamento delle informazioni o pi in
generale della definizione della soluzione di problemi assegnati. Obiettivo di tale
studio nellordine:
- lanalisi dettagliata di ci che serve al trattamento dellinformazione,
- il progetto di una soluzione applicabile alla generazione di informazioni
prodotte da altre informazioni,
- la verifica della correttezza e della efficienza della soluzione pensata,
- la manutenzione della soluzione nella fase di funzionamento in esercizio.

Se si utilizza il termine algoritmo, introdotto nella matematica per specificare


la sequenza precisa di operazioni il cui svolgimento necessario per la soluzione di
un problema assegnato, allora linformatica diventa lo studio sistematico degli
algoritmi. Va ribadito che tale definizione deve essere considerata in una accezione
pi ampia di quella che pone i calcolatori elettronici al centro dellattenzione,
sebbene linteresse principale dellinformatica quello di rendere comprensibili ed
eseguibili gli algoritmi per un sistema di calcolo.
Il calcolatore tra tutti gli esecutori di algoritmi (compreso luomo) quello
che si mostra pi potente degli altri e con una potenza tale da permettere di gestire
quantit di informazioni altrimenti non trattabili. Ed questa, forse, una delle
possibili chiavi di lettura del ruolo che linformatica va sempre pi a rivestire nelle
societ evolute. Lo studio dellInformatica considera quindi il computer come uno
scienziato utilizza il proprio microscopio: uno strumento per provare le proprie
teorie e, nel caso specifico, verificare i propri ragionamenti o algoritmi.
3.1.1. La soluzione dei problemi
In pochi anni linformatica ha fatto passi da gigante, ed naturale quindi chiedersi
quali sono stati gli aspetti che ne hanno determinato la rapida evoluzione: una
98 Capitolo terzo

evoluzione tanto rapida che oggi le macchine informatiche si incontrano in tutti i


settori produttivi.
Il primo e pi importante aspetto che le macchine informatiche risolvono un
gran numero di problemi in modo pi veloce e conveniente degli esseri umani e
trovare soluzioni a problemi una attivit che impegna luomo quotidianamente.
Senza entrare nel merito della definizione di problema, che si pu considerare
acquisita, possibile per i seguenti noti problemi:
a) preparare una torta alla frutta,
b) trovare le radici di un'equazione di 2 grado conoscendone i coefficienti
a,b e c,
c) individuare il massimo tra tre numeri,
d) calcolare tutte le cifre decimali di ,
e) inviare un invito ad un insieme di amici,
f) individuare le tracce del passaggio di extraterrestri;
osservare che:
1) la descrizione del problema non fornisce, in generale, indicazioni sul
metodo risolvente; anzi in alcuni casi presenta imprecisioni e ambiguit
che possono portare a soluzioni errate;
2) per alcuni problemi non esiste una soluzione;
3) alcuni problemi, invece, hanno pi soluzioni possibili; e quindi bisogna
studiare quale tra tutte le soluzioni ammissibili risulta quella pi
vantaggiosa sulla base di un insieme di parametri prefissati (costo della
soluzione, tempi di attuazione, risorse necessarie alla sua realizzazione,
etc.)
4) per alcuni problemi non esistono soluzioni eseguibili in tempi ragionevoli
e quindi utili.

Nel caso a), seppur ne sia noto il risultato, ossia la torta da gustare, non si
riesce a ricavare alcuna indicazione sulla ricetta da seguire che, tra laltro, non
facile individuare in un libro di cucina per la sua formulazione generica. Il caso b)
un noto e semplice problema di matematica per il quale si conosce chiaramente il
procedimento risolvente. Il caso c) un esempio di problema impreciso ed
ambiguo in quanto non specifica se va cercato il valore massimo o la sua posizione
allinterno della terna dei tre numeri assegnati. Il caso d) invece indicativo di un
problema con una soluzione nota che per non arriva a terminazione in quanto le
cifre da calcolare sono infinite. Per il caso e) si pu osservare che esistono sia
soluzioni tradizionali basate sulla posta ordinaria, che soluzioni pi moderne quali i
messaggi SMS dei telefoni cellulari o i messaggi di posta elettronica. Bisogna
quindi scegliere la soluzione pi conveniente: ad esempio quella che presenta un
costo pi basso. Il caso f) un chiaro esempio di problema che non ammette
soluzione: o, come si dice, non risolvibile.
Comunque tutti i problemi risolvibili presentano una caratteristica comune:
prescrivono la produzione di un risultato a partire da fissate condizioni iniziali
secondo lo schema di figura. Lo schema mette in luce gli elementi che concorrono
alla soluzione dei problemi. Essi sono:
- l'algoritmo, ossia un testo che prescrive un insieme di operazioni od azioni
eseguendo le quali possibile risolvere il problema assegnato. Se si indica
Algoritmi e programmi 99

con istruzione la prescrizione di una singola operazione, allora l'algoritmo


un insieme di istruzioni da svolgere secondo un ordine prefissato;

Figura 1 Algoritmo ed esempi

- l'esecutore, cio l'uomo o la macchina in grado di risolvere il problema


eseguendo l'algoritmo. Ma se un algoritmo un insieme di istruzioni da
eseguire secondo un ordine prefissato, allora lesecutore non solo deve
comprendere le singole istruzioni ma deve essere anche capace di
eseguirle. E pu eseguirle una dopo laltra secondo un ordine rigidamente
sequenziale che impone linizio dellesecuzione di una nuova istruzione
solo al termine di quella precedente; oppure pu eseguire pi istruzioni
contemporaneamente svolgendole in parallelo;
- le informazioni di ingresso (anche dette input), ossia le informazioni che
devono essere fornite affinch avvengano le trasformazioni desiderate;
- le informazioni di uscita (anche dette output), ossia i risultati prodotti
dall'esecutore del dato algoritmo.
3.1.2. La calcolabilit degli algoritmi
Lalgoritmo composto da una sequenza finita di passi che portano alla risoluzione
automatica di un problema. Il singolo passo unazione elaborativa che produce
trasformazioni su alcuni dati del problema. Da un punto di vista generale il
concetto di elaborazione pu sempre essere ricondotto a quello matematico di
funzione Y=F(X) in cui X sono i dati iniziali da elaborare, Y i dati finali o risultati e
F la regola di trasformazione.
Lautoma a stati finiti fornisce unastrazione per il concetto di macchina che
esegue algoritmi ed introduce il concetto di stato come particolare condizione di
100 Capitolo terzo

funzionamento in cui pu trovarsi la macchina. Lautoma uno dei modelli


fondamentali dellinformatica ma applicabile a qualsiasi sistema che evolve nel
tempo per effetto di sollecitazioni esterne. Ogni sistema se soggetto a sollecitazioni
in ingresso risponde in funzione della sua situazione attuale eventualmente
emettendo dei segnali di uscita, leffetto della sollecitazione in ingresso il
mutamento dello stato del sistema stesso. Il sistema ha sempre uno stato iniziale di
partenza da cui inizia la sua evoluzione. Pu terminare in uno stato finale dopo
aver attraversato una serie di stati intermedi.
Un automa M (a stati finiti) pu essere definito da una quintupla di elementi
(Q,I,U,t,w) dove:
- Q un insieme finito di stati interni caratterizzanti levoluzione del
sistema;
- I un insieme finito di sollecitazioni in ingresso;
- U un insieme finito di uscite;
- t la funzione di transizione che trasforma il prodotto cartesiano Q I in
Q (t: QxI Q)
- w la funzione di uscita che trasforma Q I in U (w: QxI U).
Il modello degli automi a stati finiti torva larga applicazione soprattutto per la
capacit di descrivere il comportamento dei sistemi. Alla formulazione matematica
si accompagna una rappresentazione grafica immediata caratterizzata da un grafo
composto da due semplici elementi: un cerchio per rappresentare gli stati del
sistema e degli archi orientati ad indicare le transizioni. Nel grafo si individuano:
- gli stati intermedi rappresentati da cerchi che hanno archi entranti ed
uscenti,
- lo stato iniziale come lunico cerchio che non ha archi entranti;
- lo stato finale, se esiste, come cerchio che non ha archi uscenti.

Figura 2 Rappresentazioni di un automa

Al grafo pu essere associata una rappresentazione pi matematica in forma


tabellare con tante righe quanti sono gli ingressi, e tante colonne quanti sono gli
stati. Nella tabella si indicano gli stati nei quali il sistema si sposta per effetto delle
sollecitazioni in ingresso con lindicazione delleventuale uscita prodotta.
Algoritmi e programmi 101

Il modello di automa fornisce un concetto generale di esecutore di azioni.


un modello generico nel quale non sono precisate la natura di ingressi ed uscite
utili ad individuare un insieme di possibili azioni elaborative.
Il modello di Macchina di Turing un particolare automa per il quale sono
definiti linsieme degli ingressi e delle uscite come insiemi di simboli; inoltre
definito un particolare meccanismo di lettura e scrittura delle informazioni. un
modello fondamentale nella teoria dellinformatica, in quanto permette di
raggiungere risultati teorici sulla calcolabilit e sulla complessit degli algoritmi.
La macchina di Turing un automa con testina di scrittura/lettura su nastro
bidirezionale potenzialmente illimitato. Ad ogni istante la macchina si trova in uno
stato appartenente ad un insieme finito e legge un simbolo sul nastro. La funzione
di transizione in modo deterministico, fa scrivere un simbolo, fa spostare la testina
in una direzione o nell'altra, fa cambiare lo stato. La macchina si compone di:
a) una memoria costituita da un nastro di dimensione infinita diviso in celle;
ogni cella contiene un simbolo oppure vuota;
b) una testina di lettura scrittura posizionabile sulle celle del nastro;
c) un dispositivo di controllo che, per ogni coppia (stato, simbolo letto)
determina il cambiamento di stato ed esegue unazione elaborativa.

Figura 3 Macchina di Turing

Formalmente la macchina di Turing definita dalla quintupla:


A, S, fm , fs, fd

dove:
- A linsieme finito dei simboli di ingresso e uscita;
- S linsieme finito degli stati (di cui uno quello di terminazione);
- fm la funzione di macchina definita come A S A;
- fs la funzione di stato A S S;
- fd la funzione di direzione A S D = {Sinistra, Destra,
Nessuna}

La macchina capace di:


- leggere un simbolo dal nastro;
- scrivere sul nastro il simbolo specificato dalla funzione di macchina;
- transitare in un nuovo stato interno specificato dalla funzione di stato;
- spostarsi sul nastro di una posizione nella direzione indicata dalla
funzione di direzione.
102 Capitolo terzo

La macchina si ferma quando raggiunge lo stato di terminazione. Definita la


parte di controllo, la macchina capace di risolvere un dato problema. Una
macchina di Turing che si arresti e trasformi un nastro t in uno t rappresenta
lalgoritmo per lelaborazione Y=F(X), ove X e Y sono codificati rispettivamente in
t e t.
Una macchina di Turing la cui parte di controllo capace di leggere da un
nastro anche la descrizione dellalgoritmo una macchina universale capace di
simulare il lavoro compiuto da unaltra macchina qualsiasi. Ma leggere dal nastro
la descrizione dellalgoritmo richiede di saper interpretare il linguaggio con il
quale esso stato descritto. La Macchina di Turing Universale diventa cos
linterprete di un linguaggio modellando il concetto di elaboratore di uso generale.
Dalla macchina di Turing discendono alcune tesi, che seppur non dimostrate,
restano fondamentali per lo studio degli algoritmi. La tesi di Church-Turing dice
che non esiste alcun formalismo, per modellare una determinata computazione
meccanica, che sia pi potente della Macchina di Turing e dei formalismi ad essi
equivalenti. Ogni algoritmo pu essere codificato in termini di Macchina di Turing
ed quindi ci che pu essere eseguito da una macchina di Turing. Un problema
non risolubile algoritmicamente se nessuna Macchina di Turing in grado di
fornire la soluzione al problema in tempo finito. Se dunque esistono problemi che
la macchina di Turing non pu risolvere, si conclude che esistono algoritmi che
non possono essere calcolati. Sono problemi decidibili quei problemi che possono
essere meccanicamente risolvibili da una macchina di Turing; sono indecidibili
tutti gli altri. Se la tesi di Church asserisce che non esiste un formalismo n una
macchina concreta che possa calcolare una funzione non calcolabile secondo
Turing, si conclude che:
- se per problema esiste un algoritmo risolvente questo indipendente dal
sistema che lo esegue se vero che per esso esiste una macchina di
Turing;
- lalgoritmo indipendente dal linguaggio usato per descriverlo visto che
per ogni linguaggio si pu sempre definire una macchina di Turing
universale.
La teoria della calcolabilit cerca di comprendere quali funzioni ammettono
un procedimento di calcolo automatico. Nella teoria della calcolabilit la tesi di
Church-Turing unipotesi che intuitivamente dice che se un problema si pu
calcolare, allora esister una macchina di Turing (o un dispositivo equivalente,
come il computer) in grado di risolverlo (cio di calcolarlo). Pi formalmente
possiamo dire che la classe delle funzioni calcolabili coincide con quella delle
funzioni calcolabili da una macchina di Turing.
La macchina di Turing e la macchina di von Neumann sono due modelli di
calcolo fondamentali per caratterizzare la modalit di descrizione e di esecuzione
degli algoritmi.
La macchina di Von Neumann fu modellata dalla Macchina di Turing
Universale per ci che attiene alle sue modalit di computazione. La sua memoria
per limitata a differenza del nastro di Turing che ha lunghezza infinita. La
macchina di Von Neumann, come modello di riferimento per sistemi non solo
teorici, prevede anche la dimensione dellinterazione attraverso i suoi dispositivi di
input ed output.
Algoritmi e programmi 103

3.1.3. La trattabilit degli algoritmi


Calcolabilit e trattabilit sono due aspetti importanti dello studio degli algoritmi.
Mentre la calcolabilit consente di dimostrare lesistenza di un algoritmo risolvente
un problema assegnato ed indipendente da qualsiasi automa, la trattabilit ne studia
la eseguibilit da parte di un sistema informatico. Infatti esistono problemi
classificati come risolvibili ma praticamente intrattabili non solo dagli attuali
elaboratori ma anche da quelli, sicuramente pi potenti, del futuro. Sono noti
problemi che pur presentando un algoritmo di soluzione, non consentono di
produrre risultati in tempi ragionevoli neppure se eseguiti dal calcolatore pi
veloce. Sono noti altri problemi la cui soluzione di per s lenta e quindi
inaccettabile.
Il concetto di trattabilit legato a quello di complessit computazionale con
la quale si studiano i costi intrinseci alla soluzione dei problemi, con l'obiettivo di
comprendere le prestazioni massime raggiungibili da un algoritmo applicato a un
problema. La complessit consente di individuare i problemi risolvibili che siano
trattabili da un elaboratore con costi di risoluzione che crescano in modo
ragionevole al crescere della dimensione del problema. Tali problemi vengono
detti trattabili. Nello studio della trattabilit delle soluzioni dei problemi due sono i
tipi di complessit computazionale fra loro interdipendenti che consentono di
misurare gli algoritmi:
- la spaziale intesa come la quantit di memoria necessaria alla
rappresentazione dei dati necessari allalgoritmo per risolvere il problema;
- la temporale intesa come il tempo richiesto per produrre la soluzione.
Oggi la complessit spaziale non viene pi presa in grande considerazione in
quanto i sistemi moderni presentano memorie di grandi capacit. La complessit
temporale un indicatore della velocit di risposta di un elaboratore (detta anche
efficienza). Si noti che lefficienza non un parametro che va considerato in
assoluto ma nel relativo del contesto nel quale il problema viene risolto. Un
algoritmo trattabile se fornisce risposte in tempi che siano utili allambiente
circostante. Tra due soluzioni di uno stesso problema una soluzione pi efficiente
dellaltra se produce i risultati in minor tempo.
La complessit di un algoritmo corrisponde quindi a una misura delle risorse
di calcolo consumate durante la computazione ed tanto pi elevata quanto
maggiori sono le risorse consumate. Le misure possono essere statiche se sono
basate sulle caratteristiche strutturali (ad esempio il numero di istruzioni)
dellalgoritmo e prescindono dai dati di input su cui esso opera. Sono invece
misure dinamiche quelle che tengono conto sia delle caratteristiche strutturali
dellalgoritmo che dei dati di input su cui esso opera.
Un primo fattore che incide sul tempo impiegato dallalgoritmo per produrre
un risultato, o tempo di esecuzione, sicuramente la quantit di dati su cui
lalgoritmo deve lavorare. Per questa ragione, il tempo di esecuzione solitamente
espresso come una funzione f(n) della dimensione n dei dati di input. Si dir, ad
esempio, che un algoritmo ha un tempo di esecuzione n2 se il tempo impiegato
pari al quadrato della dimensione dellinput. Ma da sola la dimensione dei dati di
input non basta. Infatti il tempo di esecuzione dipende anche dalla configurazione
dei dati in input oltre che dalla loro dimensione. Si possono allora fare tre tipi di
analisi:
104 Capitolo terzo

- analisi del caso migliore per calcolare il tempo di esecuzione quando la


configurazione dei dati presenta difficolt minime di trattamento. Spesso
questo tipo di analisi non fornisce informazioni significative;
- analisi del caso peggiore per calcolare il tempo di esecuzione quando la
configurazione dei dati presenta difficolt massime di trattamento. Si
tratta di unanalisi molto utile, perch fornisce delle garanzie sul tempo
massimo che lalgoritmo pu impiegare;
- analisi del caso medio per calcolare il tempo di esecuzione quando la
configurazione presenta difficolt medie di trattamento.
In generale, nel decidere come caratterizzare il tempo di esecuzione di un
algoritmo, si considerano solo quelle particolari operazioni che rappresentano il
procedimento dominante della soluzione. Ad esse si attribuisce un tempo unitario
di esecuzione per ottenere una misura indipendente da una macchina particolare.
Inoltre ci che veramente importa non la quantit precisa di operazioni effettuate
e di dati trattati, ma come questi crescono al crescere della dimensione dei dati.
Interessa cio sapere come lordine di grandezza del tempo di esecuzione cresce al
limite, ossia per dimensioni dellinput sufficientemente grandi quando la funzione
f(n) tende ai suoi asintoti. Lo studio della condizione asintotica della complessit
di un algoritmo permette ulteriormente di trascurare in un algoritmo operazioni non
significative concentrando lattenzione solo su quelle predominanti. Cos dati due
algoritmi diversi che risolvono lo stesso problema e presentano due diverse
complessit f(n) e g(n), se f(n) asintoticamente inferiore a g(n) allora esiste una
dimensione dellinput oltre la quale lordine di grandezza del tempo di esecuzione
del primo algoritmo inferiore allordine di grandezza del tempo di esecuzione del
secondo. La complessit asintotica dipende solo dallalgoritmo, mentre la
complessit esatta dipende da tanti fattori legati alla esecuzione dellalgoritmo.
Per comprendere la dipendenza della trattabilit dalla complessit temporale si
consideri un elaboratore capace di eseguire un milione istruzioni al secondo (1
MIPS). Per esso si costruisca una tabella in cui le colonne riportano possibili
dimensioni di dati di input e le righe complessit di tipo polinomiale (n, n2, n3, n5)
ed esponenziale (2n, 3n).

10 20 30 40 50
n 0,00001 sec 0,00002 sec 0,00003 sec 0,00004 sec 0,00005 sec
n2 0,0001 sec 0,0004 sec 0,0009 sec 0,0016 sec 0,0025 sec
n3 0,001 sec 0,008 sec 0,027 sec 0,064 sec 0,125 sec
n5 0,1 sec 3,2 sec 24,3 sec 1,7 min 5,2 min
2n 0,001 sec 1,0 sec 17,9 min 12,7 giorni 35,7 anni
3n 0,059 sec 58 min 6,5 anni 3,855 secoli 200.000.000 secoli

Tabella 1 Complessit temporale

Si nota che gli algoritmi di tipo polinomiale si comportano meglio di quelli


esponenziali. Ma laspetto pi evidente che gli algoritmi con complessit
esponenziale possono operare con dati di piccole dimensione: se i dati crescono
anche di poco essi diventano intrattabili.
Algoritmi e programmi 105

3.2. La descrizione degli algoritmi


Quando si affronta un problema di fondamentale importanza, qualunque sia
l'esecutore (uomo o macchina), capire preliminarmente se il problema ammette
soluzioni e successivamente, nel caso ne ammetta, individuare un metodo
risolutivo (algoritmo) e, infine, esprimere tale metodo in un linguaggio
comprensibile all'esecutore a cui rivolto.
Alcune di tali attivit rientrano nelle consuetudini della vita quotidiana. Per
rendersene conto, si pu chiedere ad un amico di correggere gli errori lessicali
presenti nel testo di figura 4a. Si pu ad esempio suggerire di svolgere una dopo
l'altra le azioni della figura 5. Ci si accorge allora di usare naturalmente costrutti
che fissano l'ordine in cui le diverse azioni devono essere svolte. Il pi semplice tra
essi quello che stabilisce che le azioni devono essere svolte una dopo l'altra.
Nell'esempio, tale ordine, fissato dalla numerazione delle frasi ma pu anche
coincidere con quello lessicografico, ossia quello che naturalmente si adotta
leggendo un qualsiasi testo nel mondo occidentale: si scorrono i righi dall'alto
verso il basso e si impone che ogni rigo contenga un comando o unistruzione per
l'esecutore. Si chiameranno tali costrutti col nome di costrutti di sequenza. Altri
costrutti possono per stravolgere l'ordine sequenziale. Nell'esempio, un altro
costrutto stabilisce che alcune azioni devono essere svolte solo se si verificano
determinate condizioni (la frase se la parola sbagliata, allora correggila,
altrimenti non fare niente prescrive la correzione soltanto in presenza di un
errore). Si chiameranno tali costrutti con il nome di costrutti di selezione. Infine, un
ultimo costrutto usato in figura dice che alcune azioni devono essere ripetute un
numero di volte prestabilito (per ogni parola del rigo fai) o determinato dal
verificarsi di certe condizioni (ripeti le azioni da 1) a 4) fino alla terminazione del
testo). Si chiameranno tali costrutti con il nome di costrutti iterativi.
In tutti gli algoritmi si possono individuare due classi fondamentali di frasi:
- quelle che prescrivono la esecuzione di determinate operazioni;
- e quelle che indicano all'esecutore l'ordine in cui tali operazioni devono
essere eseguite.
Alle prime si dar il nome di istruzioni, mentre alle seconde quello di schemi
di controllo, o istruzioni di controllo o anche strutture di controllo.
L'esempio conferma che molti degli strumenti necessari per esprimere gli
algoritmi sono noti essendo essi parte del linguaggio naturale. E tutti i linguaggi
usati per comunicare algoritmi ai calcolatori, detti linguaggi di programmazione,
avranno una parte della loro grammatica composta di costrutti del tipo di quelli
illustrati nellesempio precedente. Cambier la sintassi della specifica frase ma non
la tipologia dei costrutti disponibili anche perch, a differenza del linguaggio
naturale, per il quale il significato delle frasi dipende fortemente dal contesto in cui
vengono usate, i linguaggi di programmazione hanno una semantica indipendente
dal contesto dovendo essere chiaro e deterministico leffetto che ogni frase deve
produrre.
106 Capitolo terzo

Figura 4a - Testo da correggere Figura 4b - Testo


corretto

Figura 5 - Un algoritmo di correzione del testo di figura 2

Cos nella vita quotidiana spesso ci si confronta con descrizioni di


procedimenti da seguire (ad esempio una ricetta di cucina o il libretto di istruzione
del telefonino) o si portati a descrivere programmi di lavoro, come, in modo pi
specialistico, gli esperti dellinformatica fanno per prescrivere al calcolatore cosa
deve fare per risolvere uno specifico problema.
Nella soluzione di un problema la difficolt maggiore consiste, allora,
nell'individuare un algoritmo e nel dimensionarlo alle capacit dell'esecutore. Tra
laltro luso di un linguaggio per scrivere e comunicare algoritmi tanto pi
difficile quanto minori sono le capacit dell'esecutore a cui rivolto. Si possono
allora classificare le istruzioni anche in funzione dello stretto legame esistente tra
istruzioni e capacit dellesecutore definendo:
- elementari quelle istruzioni che l'esecutore in grado di comprendere ed
eseguire;
- non elementari quelle non note all'esecutore.
Ovviamente perch un'istruzione non elementare possa essere eseguita
dall'esecutore a cui rivolta, deve essere specificata in termini pi semplici. Il
procedimento che trasforma una istruzione non elementare in un insieme di
istruzioni elementari, prende il nome di raffinamento o specificazione
dell'istruzione non elementare. Il processo di raffinamento molto importante.
Senza di esso si dovrebbero esprimere gli algoritmi direttamente nel linguaggio di
programmazione disponibile. E molte volte tale linguaggio ha una potenza
espressiva tanto bassa o talmente artificioso da concentrare l'attenzione pi verso
le difficolt di uso del linguaggio, che verso l'individuazione dell'algoritmo
Algoritmi e programmi 107

risolutivo. Invece, l'individuazione e la descrizione di un algoritmo deve avvenire


per passi: prima lo si esprime usando il linguaggio naturale e poi si continua con un
procedimento di raffinamenti che procede fino a che tutte le istruzioni non
elementari contenute nell'algoritmo sono definite in termini di istruzioni
appartenenti al "repertorio" dell'esecutore. Ovviamente tutti i raffinamenti devono
essere integrati al posto delle istruzioni non elementari che li hanno generati perch
si abbia lalgoritmo nella versione da affidare allesecutore per lesecuzione.
3.2.1. Sequenza statica e dinamica di algoritmi
Le operazioni che compongono un algoritmo devono essere dotate delle seguenti
caratteristiche:
- finitezza: ossia devono avere termine entro un intervallo di tempo finito
dall'inizio della loro esecuzione;
- descrivibilit: ossia devono produrre, se eseguite, degli effetti descrivibili,
per esempio fotografando lo stato degli oggetti coinvolti sia prima che
dopo lesecuzione delloperazione;
- riproducibilit: ossia devono produrre lo stesso effetto ogni volta che
vengono eseguite nelle stesse condizioni iniziali;
- comprensibilit: ossia devono essere espresse in una forma comprensibile
all'esecutore che deve eseguirle.
Ne discende che lintero algoritmo, come insieme di operazioni, gode delle
stesse propriet di finitezza, descrivibilit, riproducibilit e comprensibilit.
L'algoritmo serve a risolvere un problema permettendo all'esecutore (uomo o
macchina) di eseguirlo senza essere necessariamente coinvolto nella sua
definizione. L'esecuzione di un algoritmo da parte di un esecutore si traduce in una
successione di azioni che vengono effettuate nel tempo. Si dice che l'esecuzione di
un algoritmo evoca un processo sequenziale, cio una serie di eventi che occorrono
uno dopo l'altro, ciascuno con un inizio e una fine identificabili. Si definisce
sequenza di esecuzione la descrizione del processo sequenziale. La sequenza di
esecuzione l'elenco di tutte le istruzioni eseguite, nell'ordine di esecuzione, e per
questo motivo viene anche detta sequenza dinamica. Ad esempio, nel caso
dell'algoritmo di calcolo delle radici di un'equazione di 2 grado viene evocato un
unico processo sequenziale, descritto da una sola sequenza di esecuzione che
coincide proprio con la descrizione dell'algoritmo.

Calcolo
d b 2 4ac
Calcolo la prima radice come
b d
x1
2a
Calcolo la seconda radice come
b d
x2
2a

Esempio 1 Algoritmo per il calcolo delle radici di unequazione di 2 grado


108 Capitolo terzo

L'esempio mostra un caso semplice con una sola sequenza di esecuzione.


Solitamente un algoritmo evoca pi sequenze di esecuzioni. In alcuni casi il
numero di sequenze di esecuzione pu essere infinito e il programma non ha una
terminazione. Lanalisi delle sequenze dinamiche serve proprio a capire se un dato
algoritmo abbia o meno una terminazione. Se si modifica lalgoritmo di calcolo
delle radici di una equazione di 2 grado per controllare la esistenza di radici reali,
si comincia ad osservare come esso genera comportamenti diversi dipendenti dal
valore dei dati di input a, b e c.

Calcolo
b2 4ac
Se 0
Allora Calcolo
d
Calcolo la prima radice come
b d
x1
2a
Calcolo la seconda radice come
b d
x2
2a
Altrimenti Calcola radici complesse
Esempio 2 Algoritmo per il calcolo delle radici di unequazione di 2 grado

In questo caso il processo evocato non pi fisso, ma dipende dai dati iniziali
da elaborare. Difatti sono possibili le due sequenze di esecuzione mostrate di
seguito.

Calcolo
b2 4ac
Verifico che 0 risultata vera
Calcolo
d
Calcolo la prima radice come
b d
x1
2a
Calcolo la seconda radice come
b d
x2
2a

Calcolo
b2 4ac
Verifico che 0 risultata non vera
Calcolo radici complesse

Esempio 3 Possibili sequenze di esecuzione per lalgoritmo per il calcolo delle radici di
unequazione di 2 grado
Algoritmi e programmi 109

A seconda che i valori assegnati ai coefficienti forniscano un discriminante


positivo o nullo e negativo. Uno stesso algoritmo ha quindi generato due sequenze
di esecuzione tra loro diverse. Si consideri adesso lalgoritmo seguente che
schematizza un gioco che si fa con le 52 carte francesi e che viene detto solitario
del carcerato per il fatto che non riesce quasi mai o solo dopo molto tempo.

Fintantoch (il mazzo ha ancora carte) ripeti:


Mischia le carte
Prendi 4 carte dal mazzo e disponile sul tavolo di gioco
Se (le 4 carte hanno la stessa figura) allora levale dal tavolo di gioco

Esempio 4 Algoritmo per il solitario del carcerato

Si comprende che il gioco ha termine quando tutte le carte sono state


eliminate. Si osservi che se si fortunati si eliminano le carte al primo tentativo
evocando la sequenza di esecuzione nellesempio 5, mentre, se sono uguali al
secondo tentativo si ha a sequenza di esecuzione dellesempio 6, o dopo n tentativi
usando una notazione pi sintetica si ha la sequenza nellesempio 7. Infine, el caso
sfortunato di non trovare mai quattro carte uguali si ha la sequenza di esecuzione
nellesempio 8.

Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata vera
Tolte le 4 carte dal tavolo di gioco
Esempio 5 Sequenza di esecuzione per lalgoritmo per il solitario del carcerato

Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata falsa
Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata vera
Tolte le 4 carte dal tavolo di gioco
Esempio 6 Sequenza di esecuzione per lalgoritmo per il solitario del carcerato

ripetuto n-1 volte


Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata falsa
Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata vera
Tolte le 4 carte dal tavolo di gioco
Esempio 7 Sequenza di esecuzione per lalgoritmo per il solitario del carcerato

ripetuto infinite volte


Mischiate le carte
Prese le 4 carte dal mazzo e messe sul tavolo di gioco
Verificato che (le 4 carte hanno la stessa figura) risultata falsa
Esempio 8 Sequenza di esecuzione per lalgoritmo per il solitario del carcerato
110 Capitolo terzo

L'esempio mostra che un algoritmo pu prescrivere pi di una sequenza di


esecuzione. Se poi l'algoritmo prescrive un processo ciclico, ossia la ripetizione di
un insieme di operazioni, allora pu accadere che il numero di sequenze sia
addirittura infinito. In questo caso l'algoritmo prescrive un processo che non ha mai
termine.
In un programma (che la descrizione dell'algoritmo in un linguaggio di
programmazione) la sequenza lessicografica (anche chiamata sequenza statica)
delle istruzioni (descrizione delle azioni da svolgere nel linguaggio di
programmazione) descrive una pluralit di sequenze dinamiche differenti.
Il numero di sequenze dinamiche non noto a priori e dipende dai dati da
elaborare. La valutazione sia del tipo che del numero delle sequenze dinamiche di
fondamentale importanza per valutare soluzioni diverse dello stesso problema in
modo da poter dire quale di esse presenta un tempo di esecuzione migliore, e per
poter affermare se una soluzione ha terminazione o meno.

3.3. I linguaggi di programmazione


Un programma la descrizione di un algoritmo in un linguaggio comprensibile al
processore macchina e linguaggio di programmazione tale linguaggio. Il
linguaggio di programmazione quindi una notazione formale per descrivere
algoritmi e, come ogni linguaggio, dotato di un alfabeto, un lessico, una sintassi
ed una semantica.
L'aspetto formale del linguaggio si manifesta soprattutto nella presenza di
regole rigide per la composizione di programmi a partire dai semplici caratteri
dell'alfabeto. L'insieme di queste regole costituisce la grammatica del linguaggio.
Un limitato e fondamentale insieme di queste regole definisce la struttura lessicale
del programma come formato da unit elementari, cio le parole del linguaggio. Il
lessico, quindi, permette di strutturare l'insieme limitato dei caratteri dell'alfabeto
in uno pi vasto, il vocabolario, fatto di simboli aventi una notazione pi leggibile
e comprensibile per chi li usa. L'organizzazione delle parole in frasi invece
guidata da regole che compongono la sintassi del linguaggio. Infine l'attribuzione
di un significato alle frasi oggetto delle regole semantiche.
Un programma, cos come l'algoritmo che esprime, dunque una sequenza di
frasi, ognuna delle quali specifica operazioni che l'esecutore (computer) pu
comprendere ed eseguire. La natura delle frasi o istruzioni dipende dal linguaggio
di programmazione che si usa.
Esistono oggi numerosi linguaggi di programmazione, ed ognuno ha un
proprio repertorio di istruzioni. Ovviamente, quanto pi semplice il linguaggio,
tanto pi semplice il processore che lo deve interpretare; invece quanto pi
sintetico il linguaggio, tanto pi semplice risulter descrivere un qualsiasi
algoritmo. In un computer il linguaggio pi semplice quello che direttamente
compreso dalla CPU. Chiameremo linguaggio macchina tale linguaggio per
evidenziare il suo stretto legame con l'hardware. Il linguaggio macchina offre da un
lato il vantaggio di un'alta velocit di esecuzione e di una ottimizzazione nell'uso
delle risorse hardware, e dall'altro lo svantaggio di non permettere la trasportabilit
dei programmi tra processori differenti, di non permettere cio che un programma
scritto per una data CPU possa essere eseguito da una CPU con caratteristiche
diverse. Programmare in linguaggio macchina non facile sia perch le istruzioni
Algoritmi e programmi 111

esprimono funzionalit tanto elementari che la costruzione di ogni algoritmo


richiede un gran numero di comandi, sia perch la CPU comprende istruzioni
espresse sotto forma di sequenze di bit (ad esempio 1001100111111111 potrebbe
indicare la somma di due numeri). Per superare questa ultima difficolt al
programmatore offerta la possibilit di usare i linguaggi assemblativi che pur
mantenendo uno stretto legame con le potenzialit offerte dal linguaggio macchina,
rendono la programmazione a questo livello pi agevole in quanto sostituiscono
alle sequenze di bit dei codici mnemonici pi facili da interpretare e ricordare. I
linguaggi macchina e assemblativi sono anche comunemente detti linguaggi di
basso livello, in quanto si pongono al livello della macchina. Essi, come gi
descritto, comunicano direttamente con la CPU, utilizzando i codici operativi
(binari o mnemonici) dello stesso processore, comportando difficolt di scrittura e
di verifica del corretto funzionamento dei programmi.
Per ovviare alla difficolt di programmare in linguaggi di basso livello, sono
nati dei linguaggi di programmazione che, con istruzioni pi sintetiche, ossia con
istruzioni pi vicine al tradizionale modo di esprimere i procedimenti di calcolo da
parte di un essere umano, rendono l'attivit di programmazione pi semplice; per il
fatto che questi linguaggi sono pi comprensibili dagli uomini che non dalle CPU,
li chiameremo linguaggi di alto livello. Essi fanno uso di uno pseudo-linguaggio
umano, utilizzando per il loro scopo parole-chiave o codici operativi ispirati quasi
esclusivamente alla lingua inglese. Ovviamente ci facilita molto sia la stesura che
la rilettura di un programma, ma non mette il computer in condizione di capire
direttamente il programma. Per ottenere il risultato finale dunque necessario
applicare un interprete che traduca il linguaggio simbolico e decisamente pi
sintetico, in reali istruzioni interpretabili dalla CPU. A tale scopo si potrebbe
pensare di migliorare le capacit della CPU fino a fargli interpretare il linguaggio
stesso. Ma questa risulterebbe una soluzione poco economica (per la complessit
della CPU) e poco flessibile (un piccolo cambiamento nel linguaggio
costringerebbe a buttar via la CPU). Allora la soluzione pi adottata quella di
tradurre i programmi espressi nei linguaggi di alto livello, in programmi espressi in
linguaggio macchina; la traduzione pu essere svolta dallo stesso computer che
poi esegue il programma, o addirittura da un computer completamente diverso.
Cos, la scrittura dei programmi in linguaggio di alto livello fa in modo che essi
non dipendano pi dalla CPU. E' difatti sufficiente che ciascuna CPU abbia il suo
traduttore o interprete per garantire che lo stesso programma sia eseguito da
macchine diverse. Il traduttore pi semplice chiaramente quello che mette in
corrispondenza i codici mnemonici del linguaggio assemblativo con le sequenze di
bit corrispondenti comprensibili dalla CPU: tale traduttore detto assemblatore.
Poich quanto pi di alto livello sono i linguaggi, ossia quanto pi sono vicini
all'uomo, tanto pi facile l'attivit di programmazione (svolta dall'uomo) e pi
difficile la loro traduzione (svolta dalla macchina), a partire dagli anni cinquanta
sono stati proposti e progettati, diversi linguaggi di programmazione.

3.4. I metalinguaggi
I metalinguaggi sono particolari tipologie di linguaggi con i quali si presentano le
regole della grammatica di un qualsiasi linguaggio. Un metalinguaggio descrive
quindi le propriet sintattiche e semantiche relative a un altro linguaggio o al
112 Capitolo terzo

linguaggio stesso. I pi diffusi sono la notazione Backus Normal Form o BNF e le


carte sintattiche.
La BNF, sviluppata da John Backus e Peter Naur nel 1960, prescrive che le
frasi, che compongono un linguaggio, possono essere specificate a partire da un
unico simbolo iniziale di alto livello. Ogni simbolo di alto livello (non terminale)
pu essere sostituito da una frase fra un insieme di sottofrasi, comprendenti simboli
sia non terminali che di basso livello (terminali), mentre i simboli terminali non
possono essere sostituiti. Il processo si arresta quando si ottiene una sequenza di
soli simboli terminali. Il linguaggio definito da una BNF costituito cos da tutte le
sequenze di simboli terminali ottenute applicando questo processo a partire dal
simbolo iniziale di alto livello. I simboli non terminali sono racchiusi fra parentesi
angolari (<), i terminali fra virgolette ("), le diverse alternative per un non
terminale sono separate da una barra verticale (|) e il segno := indica le possibili
sostituzioni per il non terminale. Ad esempio:

<Frase> := <Soggetto> <Predicato verbale> <Complemento oggetto>

dice che una frase si compone di un soggetto, di un predicato verbale e di un


complemento oggetto. Le espressioni che seguono specificano quali sono i
possibili valori dei simboli non terminali:

<Soggetto> := io | tu | mario
<Predicato verbale> := "mangia" | beve
<Complemento oggetto> := mela | lacqua

Una frase costruita seguendo le indicazioni delle espressioni del BNF sono
frasi corrette del linguaggio.
Una estensione della BNF, detta Extendend BNF (EBNF), stata introdotta da
Niklaus Wirth per specificare la sintassi del linguaggio di programmazione Pascal.
Nella EBNF i simboli {, }, (, ) e * possono comparire nel lato destro di una frase.
Una sequenza di simboli racchiusa fra {} indica che tale sequenza opzionale,
mentre un simbolo, o una sequenza racchiusa fra (), seguito da *, indica un
numero arbitrario di ripetizioni della sequenza, incluso 0.
Le carte sintattiche sono invece uno strumento di tipo grafico usate per la
descrizione delle regole del linguaggio. Una carta sintattica un grafo in cui linee
orientate uniscono delle scatole di formato differente: seguendo le frecce ed
interpretando opportunamente il contenuto delle scatole si costruiscono frasi
sintatticamente corrette. Le scatole rettangolari in figura sono dette simboli non
terminali in quanto il loro contenuto non compare direttamente nelle frasi. Esse
fanno riferimento ad altre carte sintattiche (quelle il cui nome coincide col
contenuto della scatola) che virtualmente devono essere inserite nella posizione
assunta dal nodo del grafo.
Le scatole tonde della figura sono dette simboli terminali n quanto contengono
caratteri o sequenze di caratteri che devono apparire direttamente nella frase, nella
posizione assunta dal nodo del grafo.
Algoritmi e programmi 113

Figura 6 Simboli Terminali (a) e Non Terminali (b) per le carte sintattiche

Le carte sintattiche non solo permettono un rapido apprendimento delle regole


della grammatica del linguaggio grazie allimmediatezza fornita dalla
rappresentazione di natura grafica, ma mettono anche nella condizione di scoprire
facilmente eventuali errori presenti nella frase. Difatti cos come data una carta si
in grado di costruire una frase corretta del linguaggio, partendo dalla frase si pu
controllarne la correttezza ed in caso negativo si pu anche individuare la causa
degli errori.

3.5. La programmazione strutturata


Un esecutore esegue un processo se:
a) stato progettato lalgoritmo che descrive come il processo deve essere
svolto;
b) l'algoritmo stato espresso con un opportuno linguaggio di
programmazione (programma) ad esso comprensibile.
Il ruolo degli algoritmi fondamentale se si pensa che essi sono indipendenti
sia dal linguaggio in cui sono espressi sia dal computer che li esegue. Si pensi ad
una ricetta per una torta alla frutta:
- il procedimento lo stesso indipendentemente dall'idioma usato;
- la torta prodotta la stessa indipendentemente dal cuoco.
Il linguaggio di programmazione e il computer hanno lo scopo di far
comprendere l'algoritmo e di far eseguire il processo. Ovviamente anche i
computer e i linguaggi di programmazione sono importanti. Gli sviluppi
tecnologici consentono di realizzare computer capaci di eseguire gli algoritmi in
modo pi rapido, pi economico e pi affidabile, rendendo esplorabili nuove aree
applicative. Linguaggi di programmazione pi sintetici rendono pi facile il
compito di esprimere gli algoritmi, con conseguente diminuzione dello sforzo
umano speso nella programmazione.
Ma tra gli aspetti o le propriet degli algoritmi da valutare con pi attenzione
sicuramente il pi importante proprio il progetto che si presenta come onerosa
attivit intellettuale (molto pi onerosa di quella di esprimere l'algoritmo con un
linguaggio di programmazione) che richiede creativit ed intuito. E si noti che non
esiste un algoritmo per il progetto degli algoritmi. Un altro aspetto la valutazione
della complessit della soluzione individuata: se ci sono algoritmi diversi per
descrivere lo stesso processo, la complessit ci dice quale di essi migliore,
ossia quale viene eseguito nel minor tempo con la minima occupazione di
memoria, pi in generale con il miglior utilizzo di tutte le risorse disponibili.
Inoltre, lo studio della correttezza un altro aspetto importante che ci consente di
valutare l'aderenza della soluzione alle specifiche del problema. Essere sicuri della
114 Capitolo terzo

correttezza un'attivit tanto pi complessa quanto pi complesso risulta il


progetto dell'algoritmo.
Negli ultimi anni, il settore informatico stato caratterizzato da un notevole
incremento della produzione del software nei riguardi dell'hardware, e questo si
riflette sui costi di un sistema informatico. Con queste premesse risulta necessario
evitare di produrre software in base alle esperienze e/o alle iniziative del
programmatore: il processo di produzione del software non pu essere un processo
di tipo artigianale (negli anni '60 il programmatore usava mille trucchi per
risparmiare memoria!), ma deve servirsi di metodologie e tecniche sistematiche di
progettazione e programmazione con fissati parametri di qualit e in maniera
standard.
La software engineering (ingegneria del software) la branca dell'Ingegneria
Informatica che raccoglie il patrimonio di metodi e tecniche per la produzione del
software.
Il processo di industrializzazione del software, introducendo metodi e
standard, riduce i margini di creativit e personalismo e consente una produzione
che soddisfi i seguenti requisiti:
- buon livello qualitativo;
- produttivit medio-alta;
- impiego di personale non troppo specializzato (la specializzazione
fornita dallo standard);
- riduzione sensibile del costo del prodotto.
In questo modo la produzione del software non risulta dissimile da quella di
un qualsiasi prodotto industriale e anche per esso si pu introdurre il concetto di
ciclo di vita. Con il termine ciclo di vita del software si intende l'insieme delle
fasi attraverso le quali si sviluppa il prodotto software come schematizzato nella
figura seguente.

Figura 7 Fasi del ciclo di vita di un software


Le fasi del ciclo di vita del software ruotano intorno all'idea di prodotto
concentrandosi sulla sua progettazione, la sua produzione e infine il suo
mantenimento in vita.
Nell'ambito della software engineering si sono sviluppate tecniche, metodi e
metodologie. Le tecniche mettono a disposizione un linguaggio e degli standard
Algoritmi e programmi 115

che supportano normalmente alcune fasi del processo di produzione del software. I
metodi consistono in un insieme di tecniche il cui uso guidato da indicazioni
formali. Le metodologie comprendono un insieme di tecniche il cui uso guidato
da regole procedurali ben precise. Le metodologie, a partire dalla formulazione del
problema, consentono:
- di individuare gli oggetti da elaborare e l'algoritmo;
- formulare oggetti e algoritmo in modo non ambiguo;
- progettare oggetti e algoritmi nell'ambiente disponibile.
L'obiettivo principale della programmazione strutturata consiste nella
costruzione di programmi con le seguenti caratteristiche:
- leggibilit: un programma non esaurisce la propria esistenza dopo poche
esecuzioni. Pertanto deve essere comprensibile ad altri programmatori;
- documentabilit: un programma deve contenere al suo interno il
significato e la motivazione di tutte le scelte di progetto effettuate;
- modificabilit: un programma deve essere strutturato in modo da
permettere un rapido adattamento ad una eventuale variazione di alcuni
parametri del progetto;
- provabilit: un programma deve essere costruito in modo da facilitare le
attivit di testing e debugging (controllo della correttezza e correzione
degli errori) fin dalle prime fasi del progetto software.
Comunque, si noti che la regola fondamentale per ottenere programmi di
buona qualit di eliminare tutti quei particolari che inficiano la costruzione logica
del programma e quindi la sua chiarezza. Le caratteristiche fondamentali della
programmazione strutturata sono:
- la modularit,
- l'uso di strutture di controllo ad un ingresso e ad una uscita,
- l'applicazione del metodo top-down o di quello bottom-up nella fase di
progettazione.
La modularizzazione ci dice che un programma deve essere composto di
moduli funzionali. Ogni modulo funzionale deve possedere un singolo e ben
precisato compito. Ogni modulo inoltre deve essere dotato di un solo ingresso e di
una sola uscita. La modularizzazione comporta una regolarit della struttura dei
programmi ed un valido supporto per la fase di progetto, in quanto rispecchia la
limitazione degli esseri umani ad esaminare un solo aspetto di un problema alla
volta.
Le strutture di controllo sono da considerarsi degli schemi di composizione
dei moduli costituenti il programma. Esse devono assolutamente avere un solo
ingresso ed una sola uscita. Tale condizione discende dalla necessit che un
modulo, composto dall'integrazione di altri moduli tramite le strutture di un
controllo, abbia un solo punto di ingresso ed un solo punto di uscita cos come i
singoli moduli componenti. Un valido contributo alla scelta delle strutture di
controllo stato apportato dal teorema di Bhm e Jacopini.
Il top-down e lo stepwise refinement costituiscono il modo procedurale di
raggiungimento della soluzione. Tale approccio parte dalla considerazione che la
complessit di un problema da risolvere non consente di tener conto
contemporaneamente di tutte le decisioni realizzative. Sar quindi necessario
procedere per raffinamenti successivi procedendo dal generale al particolare.
Ovvero: si analizza il problema al pi alto livello possibile di astrazione
116 Capitolo terzo

individuandone gli elementi pi importanti e supponendo di avere a disposizione


un sistema adatto ad eseguire gli elementi funzionali individuati. Ogni elemento,
a sua volta, diventa il problema da analizzare subendo una suddivisione in
problemi pi elementari. Il procedimento continua fino a raggiungere un livello di
scomposizione comprensibile all'esecutore (o software del sistema in uso).

Figura 8 Approccio Top Down

In figura 8 si riporta un esempio di approccio top-down e step-wise


refinement per il semplice problema del calcolo di una percentuale. Si pu notare
che si procede dal livello astratto definito dall'utente (liv. 1) fino alla procedura
dettagliata prossima all'esecutore (liv. 3) attraverso raffinamenti successivi. In
questo modo la soluzione ad un problema si presenta come un albero in cui :
- la radice corrisponde al problema,
- i nodi rappresentano le differenti decisioni di progetto,
- le foglie, infine, vengono associate alla descrizione della soluzione in
modo comprensibile all'esecutore.

Figura 9 Soluzione di un approccio Top-Down


Esiste un altro approccio per il raggiungimento della soluzione. Il metodo
bottom-up difatti parte considerando il sistema reale a disposizione e creando man
mano moduli elementari che opportunamente integrati formano moduli capaci di
compiere operazioni pi complesse. Il procedimento continua fino a quando stato
creato un modulo che corrisponde proprio alla soluzione del problema. Ripensando
alla struttura ad albero, come se si procedesse dalle foglie verso la radice.
Algoritmi e programmi 117

Figura 8 Approccio Bottom-Up

Senza entrare nel merito dei due metodi possiamo sicuramente affermare che
mentre il top-down un metodo deduttivo, quindi pi adatto alla media degli esseri
umani, il bottom-up un metodo induttivo che richiede molta intuizione ed
inventiva.
3.5.1. La progettazione dei programmi di piccole dimensioni
La progettazione dei programmi costituisce un'attivit complessa che richiede
un'attenta fase di analisi del problema e un continuo processo di valutazione delle
decisioni che in ogni momento vengono prese.
Una delle esigenze maggiormente sentita quella di una separazione netta, in
fase progettuale, tra il cosa (analisi dei requisiti e specifiche funzionali) e il
come (progetto a diversi livelli di dettaglio). In effetti la distinzione tra il cosa
e il come comune a qualsiasi tipo di progetto ed un modo per esprimere con
altre parole la separazione fra analisi e sintesi.
Nell'ambito di un progetto software, l'analisi richiede la capacit di acquisire
tutte le informazioni necessarie alla comprensione del problema e di strutturarle in
un modello che esprima, in un linguaggio adeguato, una rappresentazione coerente
e completa di cosa si deve fare.
Molto spesso, in passato, queste informazioni erano frutto di conoscenze
generiche dell'analista sui problemi da affrontare, proprio perch questa fase
veniva ritenuta di poco peso e si passava subito a scelte di realizzazione. Le
informazioni, viceversa, devono essere ricavate sia dai colloqui con gli utenti di
qualsiasi livello, ovvero con coloro che sono coinvolti nell'uso del programma sia a
livello direttivo che esecutivo, che da un esame dell'ambiente in cui il programma
sar utilizzato: queste attivit costituiscono la cosiddetta fase di definizione dei
requisiti.
Il compito dell'analista , a questo punto, quello di trarre, dall'insieme confuso
e a volte contraddittorio di esigenze ed obiettivi, un modello che esprima il
problema con un formalismo atto ad aumentarne la comprensibilit. Questo
modello dovrebbe contenere sia i requisiti funzionali (cosa deve fare il programma
118 Capitolo terzo

e su quali dati deve operare) sia i requisiti non funzionali (quali prestazioni il
programma deve offrire) e dovrebbe fornire un'indicazione delle priorit con le
quali i requisiti, funzionali o no, devono essere presi in considerazione.
Il passaggio dall'acquisizione dei requisiti alla formulazione del modello
rappresenta la cosiddetta fase di analisi di un problema.
Il progetto, invece, richiede la capacit di prendere decisioni che permettano
di realizzare un programma che soddisfi tutti i requisiti funzionali, nei limiti
stabiliti dai requisiti non funzionali. Ai fini della manutenibilit del prodotto,
necessario evitare sconfinamenti dalla fase di analisi a quella di progetto, ovvero
non si devono assumere decisioni atte a definire la soluzione di un problema
(progetto), mentre si sta ancora definendo il problema stesso (analisi); tali scelte,
infatti, sono difficilmente documentabili. In tal modo si opera una separazione, di
tipo verticale, fra le attivit di analisi e quelle di progettazione utile ad riduzione
delle difficolt. Ricapitolando, i motivi alla base di tale separazione sono:
- la possibilit di documentare in maniera completa i requisiti del problema
e quindi, indirettamente, le scelte della fase di progetto;
- l'impossibilit d'inficiare i risultati dell'analisi a causa di scelte anticipate
di progetto;
- la possibilit di rendere indipendente l'analisi dai vincoli di realizzazione.
Un'altra distinzione, in senso orizzontale, avviene fra dati e funzioni che
rappresentano gli aspetti duali, ma strettamente legati, di ogni problema che si
esamina; tale separazione dovuta sia a ragioni intrinseche, in quanto dati e
funzioni sono caratterizzati da propriet differenti, che a ragioni metodologiche in
quanto opportuno trattare separatamente funzioni da un lato e dati dall'altro, in
modo da ottimizzarne gli aspetti relativi in maniera indipendente dai
condizionamenti che, viceversa, un'analisi complessiva comporterebbe.
Ricapitolando, la separazione orizzontale tra dati e funzioni consente di ottenere:
- l'esame accurato dell'aspetto semantico dei dati;
- la completezza dell'analisi, in quanto essa viene compiuta da differenti
punti di vista;
- l'assicurazione che dati e funzioni assumano il loro giusto peso in analisi e
non si enfatizzino gli uni rispetto agli altri.
Le problematiche discusse riguardano sia i progetti software di grandi
dimensioni che la stesura di un semplice programma. Pertanto verr fornita una
descrizione di criteri applicabili alla realizzazione di programmi di piccole
dimensioni quali quelli che nel testo saranno presentati.
Linsieme di tali criteri non pu considerarsi come una metodologia di
progettazione ma un primo approccio introduttivo utile alla comprensione delle
problematiche di carattere generale. Nel seguito, i concetti introdotti verranno
applicati come approccio metodologico alla progettazione dei programmi.
In particolare le principali fasi dello sviluppo di un programma possono essere
ricondotte a:
- fase di analisi dei requisiti e delle funzioni
- fase di progetto
- analisi critica della soluzione
La fase di analisi dei requisiti e delle funzioni ha lo scopo di definire in
maniera non ambigua cosa il programma deve fare. la fase pi importante in
quanto solitamente i problemi si presentano in una forma alquanto imprecisa ed
Algoritmi e programmi 119

fondamentale che le imprecisioni siano eliminate quanto prima per evitare che esse
incidano sulle fasi successive del progetto. Infatti i costi di correzione degli errori
aumentano quanto pi si avanza nel progetto e sono massimi se erroneamente si
risolve un problema diverso da quello desiderato. allora necessario, per scoprire
le ambiguit, verificare che ogni aspetto delle specifiche abbia un'unica
interpretazione nel contesto delle applicazioni che si descrivono ed effettuare delle
esemplificazioni degli effetti delle trasformazioni tra dati di ingresso e dati di
uscita. Tali esemplificazioni costituiscono la base per un confronto del programma
con l'utente che permette di verificare la correttezza delle specifiche. Sempre ai fini
della correttezza occorrer:
- verificare che nelle specifiche non vi siano inconsistenze;
- verificare che le specifiche coprano tutte le possibili situazioni coinvolte
nel problema.
Al termine della fase di analisi si deve disporre della documentazione su:
- la definizione dei dati di ingresso al problema;
- la definizione dei dati in uscita dal problema;
- la descrizione di un metodo risolutivo che sulla carta risolva le specifiche
del problema.
La fase di progetto pu iniziare solo dopo un'accurata fase di definizione dei
requisiti e si articola nelle attivit di raffinamento successivo dei dati e
dell'algoritmo. Si gi detto che la scelta delle strutture dati con cui rappresentare
gli oggetti coinvolti dal problema gioca un ruolo fondamentale nella scelta
dell'algoritmo risolutivo. Inizialmente va scelta la struttura astratta che risponde in
maniera pi naturale alle caratteristiche del problema. Successivamente, tra tutte le
realizzazioni possibili occorrer scegliere quella che opera il compromesso ottimo
dal punto di vista della efficienza e della comprensibilit. Per affrontare la scelta in
modo adeguato fondamentale la conoscenza del numero e tipo di operazioni sui
dati previste dal problema. Costruire un algoritmo equivale ad esaminare una
specifica realt (il problema assegnato), costruirne un'astrazione, e infine
rappresentare una tale astrazione in maniera formale in un linguaggio di
programmazione. Se il problema complesso, le varie scelte di astrazione e di
rappresentazione si sovrappongono, cosicch non si riesce pi a controllare il
processo di progettazione dei programmi. In genere la mente umana non in grado
di tenere contemporaneamente sotto controllo gli effetti intrecciati di tutte le scelte
che occorre effettuare. La tecnica top-down si presenta come un approccio guida
con l'obiettivo preciso di ridurre la complessit e di offrire uno strumento di
composizione di un'architettura modulare dei programmi. Il modo di procedere di
tale approccio una diretta applicazione del metodo analitico deduttivo che si
rilevato, storicamente, il pi adatto alla media degli esseri umani. Una tale tecnica,
come gi visto. viene anche detta per raffinamenti successivi (step-wise
refinement), in quanto consente di trasformare un problema in una gerarchia di
problemi di difficolt decrescente associando a tale gerarchia di problemi una
gerarchia di linguaggi per la descrizione dell'algoritmo. Ogni passo di raffinamento
implica alcune decisioni progettuali ed importante che queste decisioni siano rese
esplicite e che il programmatore sia conscio del criterio decisionale adottato e
dell'esistenza di soluzioni alternative. Fra i criteri progettuali vanno menzionati la
chiarezza e la regolarit della struttura del programma.
120 Capitolo terzo

Un criterio guida nel processo di raffinamento per passi consiste nel


decomporre quanto pi possibile le decisioni per distinguere aspetti che sono
soltanto formalmente interdipendenti e per ritardare pi a lungo possibile quelle
decisioni che concernono dettagli di rappresentazione.
Un modo di procedere quello per livelli di astrazione. L'astrazione consiste
nell'estrazione di dettagli essenziali mentre vengono omessi i dettagli non
essenziali. Ogni livello della decomposizione presenta una visione astratta dei
livelli pi bassi in cui i dettagli vengono spinti, quanto pi possibile, ai livelli
ancora pi bassi. I moduli di livello pi alto specificano gli obiettivi di qualche
azione oppure cosa deve essere fatto, mentre quelli di livello pi basso descrivono
come l'obiettivo verr raggiunto. Il modulo pi basso nella gerarchia della struttura
del programma dipende dal linguaggio con cui si lavora. Tale approccio aiuta cos
a rendere il programma pi versatile, isolando la dipendenza dalla macchina in
pochi moduli a basso livello. Il passo di raffinamento iterativo produce, a partire
dal problema, una prima decomposizione dell'algoritmo e prosegue con successivi
raffinamenti, sempre pi vicini all'espressione finale dell'algoritmo nel linguaggio
di programmazione. E durante le varie decomposizioni possono essere introdotte
strutture dati necessarie alla definizione dell'algoritmo; per esse valgono le
considerazioni fatte sul raffinamento dei dati. Ad ogni passo della decomposizione
si devono organizzare i sottoproblemi in:
- sequenza; quando dall'analisi del problema ci accorgiamo che l'insieme
delle attivit devono essere svolte una di seguito all'altra;
- alternativa; quando si deve scegliere tra una o pi attivit;
- iterativa; quando una o pi attivit devono essere eseguite pi volte.
Ogni sottoproblema che si individua pu essere espresso con una frase del
linguaggio naturale sintetica ed espressiva (se si vogliono ritardare decisioni di
progetto relative all'implementazione della funzionalit individuata), o direttamente
con un insieme di istruzioni del linguaggio di programmazione (se la funzionalit
facilmente descrivibile). Nel primo caso il sottoproblema diventa un nuovo
problema da affrontare e per esso devono essere riapplicati tutti i criteri di
decomposizione descritti. In particolare, devono essere definite e documentate le
variabili di ingresso e di uscita e le specifiche funzionali di ogni sottoproblema.
Inoltre deve essere posta particolare attenzione alle interfacce tra i vari
sottoproblemi in modo che i risultati di un sottoproblema siano coerenti in numero
e tipo con i dati di ingresso del sottoproblema posto in sequenza.
Lanalisi critica l'ultima fase dell'approccio metodologico. In essa occorre
effettuare una minuziosa valutazione della soluzione adottata. Dapprima si verifica
la correttezza della versione dell'algoritmo, mediante ad esempio una simulazione
della sua esecuzione con il trace fissando un set di dati in ingresso.
Successivamente possibile compiere una valutazione dell'efficienza della
soluzione adottata confrontandola con altre soluzioni oppure studiando l'impatto di
una particolare scelta di progetto. Ognuna delle azioni descritte deve essere
opportunamente documentata in modo che unitamente alle specifiche iniziali si
possa avere a disposizione un insieme esaustivo di specifiche utilizzabili per una
futura ristrutturazione dell'algoritmo.
Algoritmi e programmi 121

3.5.2. La documentazione dei programmi


La documentazione dei programmi lo strumento fondamentale per la chiarezza e
la leggibilit dei programmi. Tali caratteristiche di leggibilit consentono:
1) una pi semplice comprensione di quale sia il problema che il programma
risolve e quindi della correttezza del programma stesso;
2) una pi semplice prosecuzione del progetto ogni qualvolta lo si sia
interrotto;
3) una pi elementare comunicazione delle scelte di progetto;
4) una pi semplice modificabilit del programma al variare delle specifiche
del problema.
La regola principale della documentazione, e forse anche la pi disattesa,
quella di produrre la documentazione nel corso stesso del progetto in quanto si pu
essere certi della efficacia e della completezza della documentazione soltanto se si
documentano le scelte nel momento in cui esse vengono fatte. Inoltre, un'altra
regola importante da seguire quella di inserire la documentazione quanto pi
possibile all'interno del programma in modo che viva con il programma stesso,
cambi e cresca con esso, e quindi sia sempre aggiornata.
Comunque una buona documentazione deve essere articolata in due livelli:
a) documentazione esterna del programma;
b) documentazione interna del programma.
La documentazione esterna il primo livello di documentazione e va
compilato preliminarmente nella fase di analisi dei requisiti. Essa costituisce lo
strumento fondamentale per l'utente del programma in quanto descrive soltanto il
"cosa" fa il programma e non il "come" lo fa. Inoltre, la documentazione esterna
deve segnalare dettagli operativi tali da rendere autonomo l'utente del programma
nell'uso dello stesso. Di essi i pi importanti sono:
a) funzionalit esterne;
b) attivazione del programma;
c) diagnostiche di errore;
d) configurazione richiesta del sistema;
e) indicazione sull'installazione del programma;
f) versione e data di aggiornamento.
La documentazione interna descrive la struttura interna del programma in
termini di scelte sulle strutture dati e sull'algoritmo. Tra le principali tecniche a
disposizione del progettista per generare la documentazione interna del programma
si ricordano:
1) evidenziazione della struttura del programma mediante l'uso
dell'indentazione (indentare un programma significa mettere in evidenza
nel testo del programma, mediante opportuni incolonnamenti, blocchi di
istruzioni che svolgono una funzionalit ben precisa);
2) documentazione top-down, ossia facendo comprendere come il
programma stato generato attraverso i vari raffinamenti;
3) uso di nomi di variabili autoesplicativi, ossia tali da spiegare il ruolo da
esse svolte nel corpo del programma;
4) commento del programma attraverso le frasi di commento di cui tutti i
linguaggi di programmazione sono dotati. Queste frasi consentono di
descrivere il significato di un blocco di istruzioni (motivazioni) o
122 Capitolo terzo

specificare lo stato in cui si trovano le variabili del programma tutte le


volte che il controllo passa per il punto individuato dalla linea di
commento (asserzioni).
Le motivazioni vanno utilizzate all'inizio di ciascun frammento di programma
che realizzi una funzionalit significativa. Infatti, hanno lo scopo di spiegare cosa il
frammento di programma si accinge a calcolare indipendentemente da come lo
calcola.
Le motivazioni possono essere evidenziate anteponendo alla frase di commento la
sigla M:.
(*M: calcolo la somma degli n numeri*)
(*M: inverto la matrice *)
Particolarmente importante il commento di motivazione globale posto
all'inizio del programma in cui si descrive in modo succinto il problema che il
programma risolve.
Si osservi che l'applicazione della tecnica top-down comporta una generazione
pressoch naturale dei commenti motivazione. Infatti le frasi del linguaggio
naturale, introdotte durante le fasi di decomposizione, diventano le motivazioni
delle istruzioni che le specificano nel raffinamento successivo.
Le asserzioni devono essere inserite al termine di ciascun frammento di
programma in quanto consentono di definire lo stato di una o pi variabili a seguito
dell'esecuzione delle istruzioni precedenti. Una frase di questo tipo ha lo scopo di
chiarire le relazioni logiche che sussistono tra le variabili in quel punto del
programma e pu essere espressa anteponendo alla frase di commento la sigla "A:".

(*A: X=A*2+C-1 con A>C*)

Si noti che la derivazione delle relazioni logiche tra le variabili obbliga ad una
comprensione profonda del significato del programma. Per questo motivo le
asserzioni possono essere utilizzate per effettuare una prova qualitativa della
correttezza del programma. Tra tutte le asserzioni particolare cura va posta su
quelle riguardanti le variabili di input in quanto con esse si specificano le
condizioni limite nelle quali la soluzione adottata si trover a lavorare.
Capitolo quarto

La struttura dei programmi

4.1. Le frasi di un linguaggio di programmazione


Tutti i linguaggi di alto livello prevedono quattro tipologie di frasi diverse:
- le istruzioni che tradotte in linguaggio macchina indicano al processore le
operazioni da svolgere;
- le strutture di controllo che definiscono lordine di esecuzione delle
istruzioni;
- le frasi di commento che permettono lintroduzione di frasi in linguaggio
naturale utili a rendere pi comprensibili i programmi ad un lettore
umano; le frasi di commento non vengono tradotte in linguaggio
macchina e si distinguono in asserzioni e motivazioni; le asserzioni sono
commenti destinati a fissare in un punto del programma lo stato di una o
pi informazioni. Per tale motivo permettono di chiarire le condizioni
nelle quali vengono scritte le istruzioni successive. Le motivazioni sono
invece commenti destinati a chiarire le ragioni e/o gli obiettivi per i quali
il blocco di programma, successivo al commento, viene scritto. In altri
termini, un'asserzione si riferisce alle conseguenze delle elaborazioni che
la precedono, mentre la motivazione serve a dire a priori cosa si intende
fare con le istruzioni successive;
- le dichiarazioni con le quali il programmatore da ordini al traduttore del
linguaggio di programmazione; anche le dichiarazioni non vengono
tradotte in linguaggio macchina poich servono solo a guidare il processo
di traduzione.
4.1.1. Le dichiarazioni
Tra le tante dichiarazioni le pi importanti sono quelle con cui si definiscono le
variabili sulle quali si svolgono le elaborazioni dellalgoritmo. Ad una variabile
corrisponde una porzione di memoria la cui dimensione e le cui regole di uso
dipendono dal suo tipo.
La definizione di tipo consente di introdurre dei modelli degli oggetti della
realt in quanto ne fornisce le caratteristiche strutturali (cosa sono) e funzionali (a
cosa servono).
Una definizione di tipo di dato sufficientemente rigorosa e particolarmente
adatta agli usi di tipo che verranno fatti in seguito la seguente: per tipo di dato
si intende un insieme di valori associati a delle operazioni definite su di essi. Con
124 Capitolo quarto

le definizioni di tipo possibile trattare l'informazione in maniera astratta, cio


prescindendo dal modo concreto con cui essa rappresentata all'interno di una
macchina. In molti linguaggi esistono dichiarazioni per la definizione di tipi:

type t = T;

dove t il nome del nuovo tipo e T un descrittore di tipo, cio un costrutto


sintattico del linguaggio atto a definire il tipo t. Il descrittore T pu essere anche un
tipo gi definito o predefinito nel linguaggio, in modo da introdurre dei tipi
sinonimi.
L'istanziazione di variabile avviene poi semplicemente con frasi dichiarative
come quella seguente:

var v : T;
4.1.2. Le frasi di commento
Le frasi di commento sono frasi in linguaggio naturale prive di ogni valore
esecutivo o dichiarativo che consentono di migliorare la leggibilit e la chiarezza
del programma. Si distinguono in asserzioni e motivazioni.
Le asserzioni sono commenti destinati a fissare in un punto del programma lo
stato di una o pi variabile. Per tale motivo permettono di chiarire le condizioni
nelle quali vengono ad operare le istruzioni successive. Le asserzioni sono molto
importanti per controllare la correttezza dei programmi.
Le motivazioni sono invece commenti destinati a chiarire le ragioni e/o gli
obiettivi per i quali il blocco di programma, successivo al commento, viene scritto.
Le motivazioni sono essenziali perch un programma possa essere compreso da
altre persone.
In altri termini, un'asserzione si riferisce alle conseguenze delle elaborazioni
che la precedono, mentre la motivazione serve a dire a priori cosa si intende fare
con le istruzioni successive.
Per indicare le frasi di commento si usano delle sequenze di caratteri
(marcatori) che indicano linizio (ad esempio /* in C o (* in Pascal) e la fine
(rispettivamente */ e *) ) del commento che cos pu occupare pi righi. In alcuni
casi il commento occupa un solo rigo ed allora lo si indica con un solo marcatore
iniziale (ad esempio // del C). Le due modalit possono coesistere nello stesso
linguaggio.
I linguaggi non forniscono strumenti per specificare il tipo di commento: per
farlo il programmatore potr anteporre alla frase la notazione A: per le asserzioni
e M: per le motivazioni come gli esempi seguenti illustrano.

(*A: alfa deve avere valore negativo *)


/*A: i>=10 implica condizione di errore */
x= totale / numero_elementi //M: Calcolo della media
4.1.3. Listruzione di calcolo ed assegnazione
Con le istruzioni elementari di calcolo ed assegnazione si prescrive il calcolo di
una qualche espressione con memorizzazione del risultato. In tutti i linguaggi di
programmazione essa presente in una forma del tipo:
La struttura dei programmi 125

v espressione;

che indica che lesecutore deve prima risolvere lespressione presente nel
secondo membro e, solo quando ne ha prodotto il risultato, assegnare questultimo
alla variabile v posta a primo membro. Listruzione viene anche scritta in una delle
forme seguenti:

v := espressione;
v = espressione;

Lesecuzione in due tempi distinti e successivi del calcolo dellespressione e


della memorizzazione del risultato consente di scrivere istruzioni del tipo:

i = i + 1;

Infatti lesecutore prima opera sul secondo membro (somma uno al valore di i)
e poi deposita il risultato in i: per cui se in i si trova 5 prima dello svolgimento
dellistruzione, al suo termine si trover 6.
4.1.4. I costrutti di controllo
I costrutti di controllo indicano all'esecutore l'ordine in cui le operazioni devono
essere eseguite. Essi devono essere scelti in modo da rispecchiare quanto pi
possibile i meccanismi che naturalmente vengono usati quando si descrive (ad
esempio in italiano) una qualsiasi successione di operazioni. Essi si dividono in
costrutti di sequenza, selezione ed iterazione.
Il costrutto di controllo pi semplice quello che specifica che due o pi
operazioni (elementari o no) devono essere eseguite una dopo l'altra. Tali costrutti
sono detti costrutti di sequenza e vengono indicati nel seguente modo:

PRIMA stacca il contatore


POI sostituisci la presa
QUINDI riattacca il contatore

o anche con una notazione pi compatta che usa il carattere punto e virgola
come indicatore dell'istruzione successiva:

stacca il contatore;
sostituisci la presa;
riattacca il contatore
Il punto e virgola consente anche di scrivere pi azioni della sequenza su di
uno stesso rigo:

stacca il contatore; sostituisci la presa;


riattacca il contatore
126 Capitolo quarto

Se poi si vuole marcare la funzionalit che l'intera sequenza rappresenta,


indicandone precisamente l'inizio e la fine, si possono introdurre le due parentesi
BEGIN ed END o le parentesi graffe come di seguito riportato.

BEGIN
stacca il contatore; stacca il contatore;
sostituisci la presa; sostituisci la presa;
riattacca il contatore riattacca il contatore
END

L'uso delle parentesi importante per evidenziare le varie parti dell'algoritmo


e non perdere traccia dell'eventuale processo di raffinamento, migliorando cos la
leggibilit del programma complessivo. Difatti il blocco presentato si pu vedere
come raffinamento dell'istruzione non elementare:

(*M: operazioni di intervento su presa elettrica *)

Inoltre un programma composto con blocchi di questo tipo anche pi


facilmente gestibile perch la correzione, sostituzione o modifica di un blocco
richiede un intervento locale alla coppia di parentesi.
I costrutti condizionali consentono di subordinare l'esecuzione di una certa
operazione al verificarsi o meno di una specificata condizione. La struttura di
selezione pi semplice quella tra due alternative:

SE (la carta scoperta quadri)


ALLORA vinci
ALTRIMENTI perdi

o anche, con un'altra notazione, tipica dei linguaggi di programmazione:

IF (la carta scoperta quadri)


then vinci
else perdi

In alcuni casi i costrutti di sequenza possono prescrivere la selezione di una


sola operazione:

SE (hai fame) IF (hai fame)


ALLORA mangia then mangia

Altre volte, invece, si pu voler selezionare un'operazione tra pi di due


alternative:
NEL CASO (in cui il colore del semaforo) CASE (colore del semaforo) OF
ROSSO: fermati ROSSO: fermati
VERDE: passa l'incrocio VERDE: passa l'incrocio
GIALLO: passa con prudenza o fermati GIALLO: passa con prudenza o fermati
END
La struttura dei programmi 127

I costrutti iterativi prescrivono di ripetere l'esecuzione di una o pi


operazioni; tale ripetizione viene sospesa al verificarsi di un evento. Sono costrutti
iterativi:

RIPETI REPEAT
i compiti i compiti
FINCHE' (suona la sveglia) UNTIL (suona sveglia)
MENTRE (piove) WHILE (piove)
DEVI usare l'ombrello DO usa l'ombrello
PER (10 giorni) FOR giorni:=1 TO 10
DEVI non venire all'universit DO non venire all'universit

Si osservi che il primo costrutto ha termine quando diventa vera la condizione


(la sveglia suona), il secondo invece quando la condizione diventa falsa (finisce di
piovere). Infine nel terzo esempio il numero di ripetizioni noto a priori:
chiameremo queste iterazioni cicliche o iterazioni enumerative per distinguerle
dalle altre due.
Per poter confrontare costrutti con caratteristiche simili si introducono i
concetti di equivalenza e di equivalenza funzionale. Si dicono equivalenti due
programmi che evocano le stesse sequenze di esecuzione. Sono invece
funzionalmente equivalenti due programmi che, sollecitati nello stesso modo,
producono lo stesso risultato. Si noti che due programmi equivalenti sono anche
funzionalmente equivalenti, mentre non vero, in generale, che due programmi
funzionalmente equivalenti sono anche equivalenti.
Ad esempio i due programmi seguenti:

assegna valore ad x e y; assegna valore ad x e y;


if (x<0) if (y>0)
then stampa il valore di x+y then stampa il valore di x*y
else stampa il valore di x*y else stampa il valore di x/y

producono lo stesso risultato (il prodotto di x per y) se le due variabili sono


positive, e risultati diversi negli altri casi (la somma e la divisione se sono
entrambe negative, la somma o il prodotto e il prodotto e il rapporto se il loro
segno discorde). L'equivalenza funzionale si riferisce cos ad un ben preciso
insieme di valori di ingresso: nel caso delle variabili x e y entrambe positive i due
programmi sono funzionalmente equivalenti, mentre non lo sono negli altri casi.
L'equivalenza funzionale utile per comprendere le differenze tra le varie strutture
di controllo e per giustificare da un punto di vista pratico la necessit di costrutti
simili.
Per quanto riguarda la selezione sempre possibile ricondurre un costrutto if-
then-else ad una sequenza di soli if-then. Cos il costrutto:

if (condizione)
then azione 1
else azione 2
128 Capitolo quarto

che indica che in caso di verit della condizione deve essere svolta la prima
azione, mentre in caso contrario (falsit della condizione) deve essere svolta la
seconda, funzionalmente equivalente alla sequenza dei due if-then:
if (condizione)
then azione 1
if (not condizione)
then azione 2

Le due sequenze non sono equivalenti perch in azione 1 ci potrebbero essere


operazioni che alterano il valore di condizione facendola diventare falsa generando
una sequenza di esecuzione composta da entrambe le azioni. Comunque,
disponendo della prima notazione non ha senso usare la seconda forma perch
meno chiara e sintetica. Inoltre la riscrittura di condizione crea molti problemi di
gestione quali errori che possono avvenire nella scrittura del testo, duplicazione
delle eventuali correzioni, ed altri.
La struttura case, che prescrive la valutazione di una espressione (anche detta
selettore) e la scelta dell'azione a cui stato associato il valore ottenuto da tale
valutazione, pu essere ricondotta ad un insieme di if disposti l'uno dentro l'altro.
Cos il seguente costrutto case:

case (espressione) of
a: azione 1
b: azione 2
c: azione 3
end;
equivalente a:
if (espressione=a)
then azione 1
else
if (espressione=b)
then azione 2
else
if (espressione=c)
then azione 3

che, come si nota, comporta la riscrittura dell'espressione pi volte ed una


notazione molto pi pesante. In genere non vale il passaggio inverso: ossia non
detto che una struttura di if-then-else nidificati (inseriti uno dentro l'altro)
esprimibile con un case. Ad esempio quando le condizioni degli if sono diverse
come nel caso che segue:

if (espressione 1)
then azione 1
else
if (espressione 2)
then azione 2
else
if (espressione 3)
then azione 3
La struttura dei programmi 129

Per quanto riguarda le due strutture iterative while e repeat si noti che
sempre possibile ricondurre l'una all'altra. Il while prescrive prima la valutazione
della condizione e dopo l'esecuzione delle azioni qualora il valore ottenuto sia
vero. Il ciclo termina quando la condizione diventa falsa. evidente che il ciclo
non avviene se la condizione falsa la prima volta che calcolata. Il repeat invece
prescrive l'esecuzione delle azioni e, dopo, la valutazione della condizione per cui
qualsiasi ne sia il valore, le azioni vengono eseguite almeno una volta. La
ripetizione delle azioni termina quando la condizione diventa vera. Si noti che, in
entrambi casi, quando il ciclo ha avuto inizio, si deve far in modo che le azioni
eseguite ad ogni ripetizione alterino le variabili presenti nella condizione se si
vuole che il ciclo termini. Da quanto detto discende che:

repeat Azione
azione while (not condizione)
until (condizione) do azione

sono equivalenti, cos come lo sono:

while (condizione) repeat


do azione if (condizione)
then azione
until (not condizione)

comunque evidente come le strutture presentate per prime siano da preferire


per la loro sinteticit.
Da ultimo, anche la struttura ciclica riconducibile ad una iterativa, e di
conseguenza anche all'altra. Difatti un ciclo iterativo prescrive la ripetizione di
azioni un numero di volte fissato a priori e determinato dal fatto che una variabile
detta contatore di ciclo, a partire da un valore iniziale raggiunge un valore finale o
per valori crescenti (indicato dal to) o decrescenti (indicato dal downto). Allora,
fissato vp<vg (con vp Valore Piccolo e vg Valore Grande), si ha che le strutture
nella colonna di sinistra sono funzionalmente equivalenti a quelle nella colonna di
destra.

for i:=vp to vg i:= vp;


do azione while i<vg
do begin
azione;
i:= i +1
end

for i:=vg downto vp i:= vg


do azione while i>vp
do begin
azione;
i:=i-1
End
130 Capitolo quarto

Si noti per come i costrutti ciclici sono:


- pi espressivi, perch mostrano su una sola linea tutti i parametri che
governano l'iterazione;
- pi sintetici, perch risparmiano la scrittura delle frasi di inizializzazione,
controllo e modifica della variabile;
- pi sicuri, in quanto garantiscono che non si verifichino cicli senza fine
(dovuti ad errori di gestione della variabile contatore di ciclo).
In generale i costrutti ciclici consentono anche di specificare il passo nel caso
sia diverso da 1 con una notazione del tipo:

for i:=vp to vg STEP v


do azione
for i:=vg downto vp STEP v
do azione

4.2. La potenza espressiva


Le strutture di controllo presentate sono state introdotte sulla base di motivazioni
puramente intuitive. Ci si pu chiedere se tale scelta pu in qualche modo essere
giustificata. In particolare possiamo domandarci se le strutture di controllo
introdotte siano sufficienti ad esprimere tutti gli algoritmi che possono interessare e
se siano tutte realmente necessarie.
La risposta a queste domande stata data da Bhm e Jacopini i quali, nel
1966, hanno dimostrato che in pratica tutti gli algoritmi possono essere espressi
utilizzando:
- una sola struttura di sequenza
- una sola struttura selettiva
- un sola struttura iterativa.
Difatti non a caso queste strutture possono considerarsi i tre fondamentali
modelli di pensiero mediante i quali siamo in grado di descrivere qualunque
successione di eventi. Comunque si fa generalmente uso di pi costrutti per ogni
schema di controllo scegliendo in funzione della potenza espressiva e della
praticit d'uso.
Inoltre si pu ulteriormente osservare che alcuni costrutti diventano non
necessari se si introduce nel linguaggio un meccanismo di riferimento delle
istruzioni. In alcuni linguaggi difatti possibile etichettare con un numero o un
nome le istruzioni. L'uso di tali etichette permette di prescrivere che l'istruzione
successiva da eseguire non sia quella scritta nel rigo seguente ma quella
identificata dalla etichetta specificata. Ad esempio nella sequenza:

100 azione 1;
azione 2M
if (condizione)
then goto 100;
azione 3

l'istruzione goto 100 prescrive che l'esecuzione continui con l'istruzione


etichettata con il numero 100 e non con l'azione 3. Nei linguaggi che prevedono
La struttura dei programmi 131

tali istruzioni, dette istruzioni di salto incondizionato, i costrutti sono esprimibili in


termini di if-then opportunamente composto con istruzioni di salto come di seguito
riportato:
if (condizione) if (condizione)
then azione 1 then begin
else azione 2; azione 1;
azione 3 goto 100
end;
azione 2;
100 azione 3
while (condizione) 100 if (condizione)
do azione then begin
azione;
goto 100
end
repeat 100 azione;
azione if (not condizione)
until (condizione) then goto 100
case (espressione) of if (espressione=a)
a : azione 1 then begin
b : azione 2 azione 1;
c : azione 3 goto 100
end; end
azione 4 if (espressione=b)
then begin
azione 2:
goto 100
end
if (espressione=c)
then begin
azione 3;
goto 100
end
100 azione 4

Per lunghi anni il goto stato largamente usato dai programmatori. Poi
Dijkstra, in un suo articolo del 1968, notando che la capacit dei programmatori
era inversamente proporzionale all'uso che facevano del goto, propose con molto
scalpore la sua abolizione dai linguaggi di programmazione. Egli fece notare come
l'uso indiscriminato del goto portasse a dei programmi con molti intrecci, senza un
filo logico, e quindi poco chiari. Difatti con i goto si possono creare blocchi che
hanno pi ingressi e pi uscite, possibile entrare nel mezzo di funzionalit ben
evidenziate nel testo, possibile fare tutto quanto inficia la costruzione logica di un
programma. Ad esempio, nel programma riportato di seguito, si nota come ci siano
tre punti di ingresso (istruzioni etichettate con 100, 200 e 300), due punti di uscita
(goto 400 e la fine della sequenza), e l'interruzione del for (goto 300) nonostante
tale costrutto debba essere usato quando il numero di volte sia fissato a priori.
Tutto ci non favorisce la chiarezza del testo, n una sua facile sostituzione o
modifica ed quindi da evitare. In genere l'uso di goto in avanti (verso istruzioni
132 Capitolo quarto

disposte successivamente nel testo) un modo poco ortodosso di realizzare delle


forme di selezione, mentre salti all'indietro corrispondono alla realizzazione di
forme di iterazione.

100 azione 1;
azione 2;
200 azione 3;
if (condizione 1)
then goto 200
else goto 400;
300 azione 4;
for i:=e1 to es
do begin
azione 5;
if (condizione 2)
then goto 300;
azione 6
end

In alcuni casi non si pu fare a meno di usare il goto: o perch non sono
presenti altri costrutti di controllo o perch, per ragioni dettate dal tipo di problemi
risulta necessario l'abbandono completo di porzioni di programma (per esempio per
segnalare subito errori catastrofici). In questi casi l'uso dell'istruzione di salto deve
essere valutato con molta attenzione e segnalato con appositi commenti. In
conclusione, l'uso dell'istruzione di salto non favorisce la costruzione logica
dell'algoritmo, in quanto non naturale esprimere i propri ragionamenti in termini
di interruzioni in avanti o verso cose ancora da specificare. Per tale motivo il goto
non verr usato. stato presentato in quanto in alcuni casi diventa indispensabile,
nel processo di raffinamento, per la specificazione di quei costrutti che possono
mancare nel linguaggio di programmazione usato per comunicare i programmi
all'esecutore macchina.
In tutti gli esempi che sono stati introdotti per illustrare le strutture di
controllo, stato usato il termine azione per sottolineare il fatto che in quel punto
della struttura pu comparire sia una istruzione semplice che una non elementare,
ossia composta a sua volta da un insieme di altre istruzioni e strutture di controllo.
In altre parole le strutture di controllo possono essere sia disposte in sequenza tra di
loro che inserite le une dentro le altre con una modalit detta di innestamento o
nidificazione. Se sono disposte l'una dopo l'altra, allora dopo aver espletato le
azioni indicate da una struttura di controllo, si procede prendendo in
considerazione quella ad essa successiva nella sequenza statica. Nel caso della seguente
sequenza statica:

I0;
case (espressione) of
1: if (condizione1)
then begin
I1 ;
I2
end
La struttura dei programmi 133

else I3;
5: for i:=1 to 3
do I4
8: I5
end
if (condizione2)
then I6
I f;

le sequenze di esecuzione (si) evocate sono:

s1 s2 s3 s4 s5 s6 s7 s8 s9 s10
I0 I0 I0 I0 I0 I0 I0 I0 I0 I0
I1 I3 I4 I5 I6 I1 I3 I4 I5 If
I2 I6 I4 I6 If I2 If I4 If
I6 If I4 If If I4
If I6 If
If

Tabella 1 Sequenze di esecuzioni evocate

Una sequenza di istruzioni semplici e strutture di controllo detta istruzione


composta. Una istruzione composta pu contenere al suo interno altre strutture di
controllo con una gerarchia che a livello pi alto termina nel programma. Poich le
istruzioni semplici (l'assegnazione di valore, le procedure di input e di output e le
operazioni definite sulle strutture dati) presentano un solo punto di ingresso e di
uscita, la loro composizione attraverso le strutture di controllo IF-THEN-ELSE, IF-
THEN, CASE, REPEAT-UNTIL, WHILE-DO, FOR-DO, porta ad una istruzione
composta che mostra complessivamente ancora un unico punto di ingresso e di
uscita. Tale caratteristica viene anche indicata dicendo che la sequenza di tipo
one-in e one-out. Luso di istruzioni di salto impedisce la costruzione di sequenze
di tipo one-in e one-out.
Il numero di sequenze di esecuzione evocate da una istruzione composta
dipende dal tipo di strutture di controllo utilizzate e dal modo in cui queste sono
composte tra loro. Se le strutture sono considerate singolarmente, allora entrambe
le strutture di controllo IF-THEN-ELSE e IF-THEN generano due sequenze di
esecuzione, il CASE tante sequenze di esecuzione quanti sono i valori previsti per
il selettore, mentre non possibile fare previsioni per le strutture iterative in quanto
il numero di sequenze di esecuzione dipende dai valori che assumono le variabili di
controllo dei cicli durante l'esecuzione. Le tabelle seguenti riportano alcuni
semplici esempi di strutture di controllo selettive disposte in sequenza e innestate
con a lato tutte le sequenze di esecuzione evocate.

s1 s2 s3 s4
if ca then I1 I1 I1 I2 I2
else I2; I3 I4 I3 I4
if cb then I3 If If If If
else I4;
If ;
134 Capitolo quarto

s1 s2 s3 s4 s5 s6 s7 s8
if ca then I1 I1 I1 I1 I1 I2 I2 I2 I2
else I2; I3 I3 I4 I4 I3 I3 I4 I4
if cb I5 I6 I5 I6 I5 I6 I5 I6
then I3 If If If If If If If If
else I4;
if cd
then I5
else I6;
If ;
Tabelle 2,3 Istruzioni selettive e sequenze di esecuzioni evocate

Due sequenze per un solo if, quattro per due e otto per tre. Si comprende
allora che una sequenza di n strutture selettive evoca 2n sequenze di esecuzione.

s1 s2 s3
if ca then I1 I2 I3
if cb then I1 If If If
else I2;
else I3;
If ;

s1 s2 s3 s4
if ca then I1 I2 I3 I4
if cb then If If If If
if cd then I1
else I2;
else I3;
else I4;
If ;

Tabelle 4,5 Istruzioni selettive e sequenze di esecuzioni evocate

Due sequenze per un solo if, tre per due e quattro per tre. Si comprende che un
tale innesto di n strutture selettive evoca n+1 sequenze di esecuzione.
Infine, la tabella seguente mostra come, nel caso di strutture iterative, il
numero di sequenze di esecuzione vari in funzione dei valori assunti, durante
l'esecuzione, dalle variabili che governano le condizioni della iterazione.

s1 s2 s3
I0; (ei=1 es=3) (ei=8 es=0) (ei=50 es=50)
for i:=ei to es I0 I0 I0
do I1; I1 If I1
If ; I1 If
I1
If
Tabelle 5 Istruzioni iterative e sequenze di esecuzioni evocate
La struttura dei programmi 135

4.3. La modularit
L'applicazione della tecnica top-down permette di ottenere un programma
composto da moduli funzionali, ciascuno dei quali realizza un singolo e ben
preciso compito e avente un solo punto di ingresso e di uscita. Una tale
impostazione risulta un valido aiuto per l'attivit di programmazione, in quanto
rispetta la limitazione degli esseri umani che, solitamente, sono in grado di
esaminare un solo aspetto di un problema alla volta. Inoltre, il processo di
raffinamento iterativo produce una gerarchia di sottoproblemi di complessit
decrescente associando ad essa una gerarchia di linguaggi per la descrizione
dell'algoritmo. Difatti, ogni sottoproblema individuato viene espresso con una frase
del linguaggio naturale sintetica ed espressiva per ritardare decisioni di progetto
relative alla sua implementazione.
Un tale approccio per non consente ancora di affrontare in modo adeguato i
seguenti problemi:
- evitare riscritture inutili di sottoproblemi dello stesso tipo in pi punti del
programma;
- integrare soluzioni gi disponibili di alcuni sottoproblemi;
- non perdere la sinteticit delle frasi in linguaggio naturale introdotte per
descrivere i vari sottoproblemi;
- verificare la correttezza della soluzione del problema per passi: ossia
verificando dapprima la correttezza dei singoli sottoproblemi e
successivamente dell'insieme da essi costituito.
Per ovviare a tutti questi inconvenienti, i linguaggi di programmazione
prevedono l'uso dei sottoprogrammi: permettono cio di assegnare ad una sequenza
di istruzioni un nome che pu essere utilizzato come sua abbreviazione e che pu
essere inserito al suo posto nel programma. Inoltre se il nome rappresenta anche un
risultato, che pu essere inserito in una espressione o in una istruzione, il
sottoprogramma viene chiamato funzione: in tutti gli altri casi viene anche
chiamato procedura.
L'indicazione del nome di un sottoprogramma viene chiamata indicazione
della procedura (o della funzione) corrispondente; l'impiego di una procedura in
un programma viene detto chiamata della procedura (quello di una funzione,
viene detto chiamata della funzione). L'indicazione del sottoprogramma si
compone di due parti: il titolo e il corpo. Nel titolo del sottoprogramma vengono
indicati il suo identificatore (il nome) e altre informazioni; il corpo si compone
della sequenza di dichiarazioni e istruzioni denotata dal nome.

sottoprogramma prepara_la crema;

SP1: sbatti le uova con lo zucchero;


SP2: stempera la farina nel latte;
SP3: versa il latte nelle uova e nello zucchero;
SP4: mette sul fuoco e porta ad ebollizione

Queste convenzioni, osservando con attenzione il mondo che ci circonda, si


presentano spesso anche nella realt. Difatti, anche nella vita reale si fa
136 Capitolo quarto

frequentemente ricorso ai riferimenti come mezzo per evitare inutili ripetizioni. Si


consideri per esempio il testo della ricetta di una torta alla frutta ricopiato da un
libro di cucina:

programma torta;

I1: impasta farina, burro, uova, sale e zucchero fino ad ottenere una pasta soffice;
I2: inforna la pasta per una decina di minuti;
I3: prepara_la_crema;
I4: distribuisci la crema sulla sfoglia;
I5: ricopri con frutta di stagione tagliata a dadini e gelatina

Si pu notare nella terza istruzione l'uso del riferimento ad un'altra ricetta che
ha permesso allo scrittore di non perdere tempo a ricopiarla. L'esecutore della torta
dovr:
- spostarsi sulla ricetta della crema quando trova il suo riferimento;
- riportarsi sulla ricetta della torta nel momento in cui ha terminato la
crema.
In altre parole la chiamata di procedura pu essere vista come una particolare
istruzione di salto alla prima istruzione della procedura. La terminazione della
procedura genera poi un ulteriore salto alla istruzione successiva a quella di
chiamata.

Figura 1: Chiamata a Sottoprogramma

Cos la sequenza dinamica evocata in figura risulta essere:

I1;I2;SP1;SP2;SP3;SP4;I4;I5;

L'uso dei sottoprogrammi non serve solo ad abbreviare il lavoro di scrittura,


ma anche, e in modo essenziale, ad articolare, suddividere e strutturare un
programma in componenti fra loro coerenti. Un'adeguata struttura determinante
per la comprensibilit di un programma, soprattutto quando esso complicato e il
La struttura dei programmi 137

testo ha dimensioni che non permettono di scorrerlo con un unico sguardo.


Un'appropriata articolazione in sottoprogrammi indispensabile per una
documentazione comprensibile e per una facile verifica. Difatti un sottoprogramma
rappresenta una precisa e definita funzionalit ed dotato di una altrettanto ben
definita interfaccia con l'esterno. Entrambe queste propriet ne garantiscono il
controllo di correttezza a prescindere dal resto del programma che ne fa uso. Cos
un programma organizzato in sottoprogrammi pu essere pi facilmente
controllato di un programma senza sottoprogrammi per il fatto che l'attivit di
controllo viene dapprima esercitata sui singoli sottoprogrammi e successivamente
sulla loro integrazione. Per questi motivi utile indicare una sequenza di istruzioni
con un sottoprogramma (assegnandole quindi un nome) anche se essa compare in
un sol punto del programma e l'introduzione del sottoprogramma non porta ad un
testo pi breve. L'adozione del metodo top down comporta un uso naturale dei
sottoprogrammi.
Difatti nel passaggio da una frase del linguaggio naturale, o istruzione non
direttamente esprimibile nel linguaggio di programmazione, al suo raffinamento si
pu far ricorso all'uso di procedure o funzioni ogni qualvolta la funzionalit
individuata:
1 viene utilizzata in varie sezioni del programma;
2 gi disponibile in una libreria di sottoprogrammi (si pensi ad una
funzione matematica);
3 pu essere utile inserirla in una libreria per poterla riutilizzare in altri
progetti;
4 dipende fortemente dal particolare elaboratore che si sta usando (si pensi a
funzioni di gestioni del video);
5 deve nascondere le modalit con cui vengono effettuate le elaborazioni al
suo interno, al fine di eliminare turbative all'ambiente esterno per
modifiche interne. Ci viene realizzato mediante comunicazioni con
l'ambiente esterno che avvengono solo attraverso un numero ristretto di
parametri;
6 deve contenere astrazioni sui dati in modo che si possa interagire con un
particolare tipo di dato soltanto attraverso le operazioni di accesso al tipo
senza interessarsi della reale rappresentazione del dato stesso.

L'applicazione di questi criteri consente non solo di migliorare la


comprensibilit del programma finale ma anche e soprattutto la verificabilit (in
particolare i punti 1, 2, 5 e 6), la manutenibilit (in particolare 1, 5 e 6) e la
portabilit del prodotto software (punto 4). Inoltre procedendo in tale modo pi
facile pensare alla suddivisione del lavoro, nel caso in cui il progetto deve essere
realizzato da pi persone.

4.3.1. La parametrizzazione del codice


Spesso accade che una sequenza di istruzioni compaia in punti diversi non
esattamente nella stessa forma. Particolare attenzione merita il caso in cui la
differenza consiste nell'uso di operandi diversi. Si considerino ad esempio le due
sequenze:
138 Capitolo quarto

stampa('Dammi riempimento:'); stampa('Numero fogli :');


leggi(riemp) leggi(num_fogli)

esse differiscono per le costanti da stampare ('Dammi riempimento :' e 'Numero


fogli :') e per le variabili di cui si devono leggere i valori (riemp e num_fogli).
Allora possibile estrarre uno schema comune:

stampa(messaggio);
leggi(x)

definendo parametricamente le variabili su cui operare. In questo modo


possibile ricondurre le due sequenze allo schema, a patto che si crei un
meccanismo che associ rispettivamente:

'Dammi riempimento :' => messaggio


x => riemp

'Numero fogli :' => messaggio


x => num_fogli

Difatti messaggio deve ricevere la stringa da stampare mentre x deve restituire


il valore letto. Si noti che messaggio un parametro di input alla procedura mentre
x di output per essa.
Si definiscono parametri formali gli oggetti utilizzati nel corpo della
procedura. Essi devono essere indicati nel titolo della procedura. Gli oggetti da
sostituire al posto dei parametri formali, prima di ogni esecuzione, sono detti
parametri attuali (o effettivi) e devono essere specificati in ogni chiamata della
procedura o della funzione. Si osservi che esistono vari modi di sostituire i
parametri formali in quelli effettivi.
In altre parole, i parametri formali indicano genericamente su quali oggetti il
sottoprogramma deve agire (servono quindi alla sua formulazione), mentre quelli
effettivi precisano gli oggetti sui quali il sottoprogramma deve effettivamente
operare.
I parametri formali devono riferirsi, durante l'esecuzione del sottoprogramma,
ai parametri effettivi e, perch ci possa avvenire, devono essere compatibili con
essi (devono essere dello stesso tipo). Nel titolo della procedura si specificano sia il
tipo dei parametri che il meccanismo di sostituzione. Inoltre, tra il titolo della
procedura e la sua chiamata si stabilisce una corrispondenza tra la lista dei
parametri effettivi e formali di tipo posizionale: cio al primo parametro effettivo
viene fatto corrispondere il primo parametro formale, e cos via per i successivi. I
parametri attuali devono quindi essere forniti con rispetto di numero, tipo e ordine
rispetto a quelli formali.
La struttura dei programmi 139

I meccanismi di sostituzione, in generale ricadono in una delle tre seguenti


tipologie:
- per valore
- per riferimento (o per variabile)
- per nome.
La sostituzione per valore assegna al parametro formale del sottoprogramma il
valore del corrispondente parametro effettivo. Il parametro effettivo pu essere
un'espressione e come casi particolari di espressione una costante o una variabile.
Se un'espressione, se ne calcola il valore e lo si assegna al corrispondente
parametro formale.
Nel caso di variabili, la sostituzione per valore garantisce che la variabile
passata come parametro effettivo non venga alterata dal sottoprogramma. Difatti il
sottoprogramma dopo averne copiato il valore nella corrispondente variabile
formale al momento della chiamata, lavora in sostanza su una sua copia (il
parametro formale) e tutte le modifiche effettuate su tale copia non riguardano in
alcun modo il corrispondente parametro effettivo. Questo significa che, se il
parametro formale di tipo strutturato, all'atto della chiamata l'intera struttura del
parametro effettivo viene ricopiata in esso, con conseguente consumo di tempo
(per la copiatura) e di spazio (per l'occupazione di memoria della struttura).
La sostituzione per riferimento fornisce al parametro formale del
sottoprogramma l'indirizzo di memoria del corrispondente parametro effettivo. Per
tale motivo il parametro effettivo pu essere soltanto una variabile. A differenza
della sostituzione per valore, il parametro formale non una copia ma
un'informazione attraverso cui si lavora direttamente sul parametro effettivo (tipo
puntatore) e la sua occupazione di memoria esclusivamente quella necessaria per
contenere l'indirizzo del parametro attuale.
Esiste un ultimo meccanismo di sostituzione detto per nome che citiamo
soltanto per motivi di completezza, in quanto, ormai, lo si ritrova soltanto in
pochissimi linguaggi.
Secondo tale modalit il parametro formale viene testualmente sostituito dai
parametri effettivi senza alcuna valutazione. Questa modalit differisce
sostanzialmente dai precedenti meccanismi in quanto la sostituzione non avviene
all'atto della chiamata del sottoprogramma ma durante la sua esecuzione, ogni
qualvolta che esso fa uso del parametro formale. In altre parole come se
avvenisse una riscrittura del sottoprogramma ad ogni sua attivazione in modo da
sostituire l'identificatore del parametro formale con quello del corrispondente
effettivo.
Per quanto concerne la scelta del metodo di sostituzione dei parametri si
possono seguire le seguenti regole base:
- quando un parametro rappresenta un argomento di una procedura, si
sceglie la sostituzione per valore;
- quando un parametro rappresenta invece un risultato, occorre usare la
sostituzione per riferimento.
Osservando tale problema da una visuale differente, possiamo classificare i
parametri formali in:
- parametri di ingresso al sottoprogramma;
- parametri di uscita dal sottoprogramma;
- parametri modificabili dal sottoprogramma.
140 Capitolo quarto

I primi sono parametri che forniscono i valori a partire dai quali il


sottoprogramma inizia le proprie elaborazioni; i secondi rappresentano i risultati da
esso prodotti; infine i terzi sono quelli che contemporaneamente possono
considerarsi di ingresso e di uscita. Una tale classificazione ci torna utile per la
scelta del meccanismo di sostituzione. Difatti, se i parametri sono solo di ingresso
al sottoprogramma conviene scegliere la sostituzione per valore che impedisce
l'alterazione dei parametri effettivi da parte del sottoprogramma. Invece, negli altri
due casi, affinch dopo la chiamata possano essere visibili gli effetti del
sottoprogramma, si deve scegliere la sostituzione per riferimento che permette alle
istruzioni del sottoprogramma di lavorare direttamente sui parametri effettivi. Per
comprendere il modo di scegliere il meccanismo di sostituzione si pensi, ad
esempio, ad uno scrittore che per svolgere la sua attivit pu aver bisogno di un
correttore di bozze per eliminare errori di stampa e di un traduttore per la
traduzione del libro in una lingua diversa dalla sua. Mentre il primo deve produrre
il libro corretto, il secondo deve fornire anche un nuovo libro: quello tradotto. Se
l'autore accorto, prima di fornire il libro al traduttore, ne effettua una fotocopia in
modo che l'originale non sia alterato da qualsiasi cosa il traduttore possa aver
intenzione di fare. Invece, nel caso del libro da correggere, l'autore deve lavorare
sullo stesso libro del correttore se vuole controllare il lavoro svolto. In altri termini,
la sostituzione per riferimento permette a due unit di condividere una stessa area
di lavoro, mentre quella per valore le separa anche se assumono lo stesso valore
solo all'atto dell'attivazione del sottoprogramma.
4.3.2. Le funzioni
Ricapitolando, le procedure sono caratterizzate dalle seguenti propriet:
- sono unit di programma subordinate che sono eseguite solo mediante una
esplicita attivazione (chiamata);
- svolgono compiti ben definiti;
- si interfacciano con il programma chiamante scambiando con esso
informazioni mediante il meccanismo di sostituzione per valore o per
riferimento (accettando cos le informazioni da elaborare e restituendo i
risultati della loro elaborazione).
Da un punto di vista sintattico le procedure sono caratterizzate da:
- dichiarazione del titolo e del corpo;
- esplicitazione nel titolo del meccanismo di sostituzione e del tipo dei
parametri formali;
- meccanismo di attivazione.
Quando una procedura fornisce un unico risultato, pu essere organizzata in
funzione, in modo che il suo nome corrisponda proprio a tale risultato. Per
esempio, si organizzano in funzione le funzioni matematiche che devono essere
utilizzate all'interno del programma, o le espressioni o porzioni di esse che devono
essere usate pi volte all'interno di uno stesso programma. In tali casi si ha che:
- il nome della funzione l'unico risultato della procedura;
- poich il nome della procedura una variabile a tutti gli effetti, deve
essere dotata di un tipo che deve essere indicato nell'intestazione;
- si pu assegnare valore a tale variabile soltanto nel corpo della funzione
stessa;
La struttura dei programmi 141

- si pu usare il valore di tale variabile inserendone il nome in espressioni


che si trovano all'esterno del corpo della funzione.
In altri termini la presenza all'interno della funzione del suo stesso nome nella
parte sinistra di un'assegnazione permette di trasmettere al programma chiamante il
risultato delle sue elaborazioni, mentre il richiamo della funzione avviene
inserendone semplicemente il nome in una espressione. Per esempio, una funzione
che calcola la somma di due numeri potrebbe essere definita da:

funzione somma(a,b:interi):intera

somma:=a+b

ed essere utilizzata in uno dei seguenti modi:


x:=a+somma(3,y)/3;
stampa(somma(alfa,beta));
Ad esempio, considerate le seguenti istruzioni:

Y:=3*XA+LN(XB-1); K:=SIN(Y)+3*Z+LN(M-1)+X;
WRITELN(SQR(X)-9*X+SQR(X1)-10*X1);
Y:=SIN(X0-Y0/2)*9+5*(SIN(X0+1-(Y0+1)/2)*3);
se vengono definite le tre funzioni riportate di seguito:

funzione F1(x,y:reale):reale; funzione F2(x,y:reale):reale; funzione F3(x,y:reale):reale;

F1:=3*x+LN(y-1); F2:=SQR(x)-9*x ; SIN(x-y/2)*3;

si possono riscrivere nella forma:

Y:=F1(XA,XB); K:=SIN(Y)+F1(Z,M)+X;
WRITELN(F2(X)+F2(X1)-1);
Y:=F3(X0,Y0)*3+5*F3(X0+1,Y0+1);

con un notevole miglioramento della leggibilit del programma.


Tutti i linguaggi di programmazione mettono a disposizione del
programmatore alcune procedure o funzioni che sono gi definite all'interno del
linguaggio stesso. Esse non richiedono una definizione esplicita e per questo
motivo vengono anche dette implicite o standard. Solitamente sono funzioni
standard le funzioni matematiche per il calcolo del seno, coseno, logaritmo
naturale, l'esponenziale, il valore assoluto di un numero, la radice quadrata, il
quadrato, ed altre ancora. In genere si ritrovano come funzioni o procedure
predefinite quei moduli che hanno un'alta probabilit di essere impiegati in alcuni
campi di applicazione, per cui lasciarne la definizione all'utente risulterebbe
dispendioso in termini di efficienza complessiva, o che sono indispensabili per la
completa definizione e utilizzazione di costrutti del linguaggio, come per esempio
si pu osservare a proposito dei meccanismi di ingresso e uscita.
D'altro canto si pu notare come l'uso dei sottoprogrammi sia un potente
strumento di espansione del linguaggio in quanto consente al programmatore
l'inserimento in esso di nuove parole chiave, i nomi delle procedure e delle
142 Capitolo quarto

funzioni appunto, la cui definizione indipendente dagli algoritmi che ne fanno


uso. Cos possibile costruire linguaggi con potenze espressive sempre maggiori
con una notevole facilitazione dell'attivit di programmazione. Si noti infine che
tale modo di procedere alla base del metodo induttivo di risolvere i problemi, che
stato chiamato approccio bottom-up.
4.3.3. La visibilit
L'uso dei sottoprogrammi permette di costruire programmi non costituiti da
una unica sequenza di istruzioni ma da una gerarchia di unit di programma (o
anche macchine astratte se si vuole dare risalto al cosa fanno e non al modo in cui
sono realizzate), ciascuna delle quali realizza una particolare istruzione in modo
autonomo, con proprie definizioni di tipi, dichiarazioni di variabili e istruzioni.
Ogni unit forma la base per il livello superiore (le unit che la chiamano) e si
appoggia eventualmente su un livello di macchina inferiore (le procedure che sono
chiamate al suo interno).
Si chiamer programma principale quella unit di programma che si
interfaccia direttamente con il sistema operativo. Nella figura, per esempio, A pu
essere il programma principale, A.1 e A.2 il livello su cui A si appoggia, e A.1.1 e
A.1.2 quello su cui si appoggia A.1.

A.1.1

A.1

A.1.2
A

A.2

Figura 2: Struttura di un prgramma

Evidenzieremo il programma principale racchiudendone gli elementi


fondamentali (dichiarazioni ed istruzioni) nella struttura:

program .......;
.....
begin
.....
end.

mentre abbiamo racchiuso gli elementi di una procedura nella struttura:


La struttura dei programmi 143

procedure .... (...);


.....
begin
.....
end;

o in:

function ..... (....): ....;


.....
begin
.....
end;

se si tratta di una funzione.


In tale ambito, un altro concetto fondamentale sottolinea il ruolo delle
procedure dal punto di vista della strutturazione dei programmi. Spesso, certe
variabili vengono impiegate soltanto all'interno di una sequenza di istruzioni,
mentre non hanno influenza nel resto del programma. La comprensione del
programma aumenta considerevolmente quando la localizzazione delle variabili
posta in chiara evidenza. In ogni caso, i campi di influenza delle variabili (cio,
dove il loro valore influenza l'esecuzione) devono risultare con chiarezza dalla
struttura del programma. Con il termine regole di scope (visibilit) si fa riferimento
a quelle regole che consentono di determinare i campi di influenza di una variabile
(e pi in generale di un qualunque oggetto) in tutte le varie parti costituenti un
programma. In altre parole si dice scope di un oggetto la porzione di programma
che in grado di usarla.
L'organizzazione in procedure il modo pi opportuno per porre in risalto lo
scope degli oggetti. Se un oggetto - una costante, un tipo, una variabile, una
procedura o una funzione - definito ed usato solo all'interno di una determinata
procedura, allora viene detto locale a quella procedura. Se, viceversa, definito
nell'unit di programma chiamante, ma risulta visibile alla procedura chiamata
attraverso qualche meccanismo, allora viene detto non locale alla procedura. Infine
viene detto globale se risulta definito nel programma principale, perch tale unit
l'unica che pu rendere visibili i propri oggetti a tutte le altre in quanto rappresenta
la radice della gerarchia di unit di programma (in altre parole l'unica unit che
chiama tutte le altre e che non chiamata da nessun'altra).
Si noti che non esiste alcun inconveniente se si sceglie per un oggetto locale
ad una unit di programma lo stesso identificatore utilizzato per un altro oggetto di
una diversa unit di programma. Tale caratteristica molto importante poich
senza di essa non soltanto i rischi di errori sarebbero molto grandi, ma sarebbe
praticamente impossibile impiegare procedure di biblioteca o procedure scritte da
altri. Tale propriet deriva dal fatto che un oggetto locale ad una unit di
programma visibile soltanto dalle istruzioni di tale unit e pertanto qualsiasi
modifica del suo valore non visibile all'esterno di essa.
Per quanto riguarda invece gli oggetti che genericamente abbiamo detto non
locali o globali esistono dei meccanismi espliciti o impliciti che consentono di
estendere la visibilit di un oggetto a pi unit di programma. Tali meccanismi
144 Capitolo quarto

differiscono in funzione del modo in cui, nei vari linguaggi, possibile organizzare
il programma nella gerarchia di unit di programma illustrata precedentemente.
A tale proposito esistono dei linguaggi (es. Pascal, Basic) in cui i vari
sottoprogrammi (e funzioni) possono essere inseriti nel corpo del programma
principale. Si definiscono sottoprogrammi interni tale tipo di sottoprogrammi.
Viceversa, esistono altri linguaggi (ad esempio il FORTRAN) in cui ogni unit di
programma risulta staccata da tutte le altre unit di programma. Si definiscono
sottoprogrammi esterni tale tipo di sottoprogrammi.
Nei sottoprogrammi interni esistono dei meccanismi impliciti tali che, se un
identificatore definito in una unit di programma, visibile, allora, da tutte le
unit interne ad essa, salvo il caso in cui esso sia ridefinito localmente in una di
queste unit interne. Ad esempio nel programma:

Procedure A;
var x,y:integer;
......
procedure B;
var x:integer;
......
......
begin
......
......
end;
......

begin
......
B;
......
end;

la variabile y di A visibile da B, mentre la x di A non lo in quanto la


dichiarazione di una variabile in B con lo stesso nome ha creato una variabile locale
che nasconde quella definita nella procedura chiamante. In altre parole, tutte le
modifiche fatte da B su y influenzano anche A, mentre quelle effettuate su x da B
non si propagano sulla x di A (in quanto sono due variabili differenti).

Nei sottoprogrammi esterni esistono invece dei meccanismi espliciti che


consentono a pi unit di programma di condividere gli stessi oggetti. Ad esempio
in FORTRAN possibile, con opportune dichiarazioni, raggruppare un insieme di
variabili in un'area di memoria prestabilita. Tale area viene chiamata common
(comune) perch condivisibile da pi unit di programma differenti. L'uso del
common in due unit di programma ha l'effetto di assegnare le medesime locazioni
di memoria (quelle dell'area common appunto) a variabili dell'uno e dell'altro. Ad
esempio l'uso del common nelle quattro unit di programma di seguito riportate:
La struttura dei programmi 145

program main
integer a,b,c,d
common a,b,c,d
......
end

subroutine sub1
integer x,y
common x,y
......
end

subroutine sub2
integer i
common i
......
end

subroutine sub3
integer k,f,v,z
common k,f,v,z
......
end

comporta che a, x, i, e k sono associati alla prima locazione di memoria


dell'area common; b, y, e f alla seconda; c e v alla terza; e infine d e z alla quarta.
La globalit di una variabile risulta cos definita dalla posizione assunta nella zona
comune e non dal nome delle variabili.
Ricapitolando, una procedura pu usare una variabile dell'unit di programma
chiamante o attraverso la corrispondenza parametri formali ed effettivi o mediante
l'uso delle regole di visibilit collegate ai meccanismi di comunanza. Senza
pretendere in questa sede di discutere a fondo i vantaggi e gli inconvenienti dei due
metodi, bene fare le osservazioni generali, riportate di seguito.
- A priori, se alcuni argomenti devono essere trasmessi in molte chiamate,
la trasmissione esplicita pu essere pesante e fastidiosa. Un meccanismo
di comunanza dati preferibile soprattutto per le procedure chiamate
frequentemente perch pi veloce.
- Per altro si deve notare che l'utilizzazione del meccanismo di comunanza
dati corrisponde a passare implicitamente dei dati del tipo dati
modificabili. Cresce cos il rischio di introdurre nella procedura
chiamante errori indesiderati e difficilmente controllabili se la visibilit
delle variabili molto estesa: errori che derivano dalla possibilit offerta
alle procedure chiamate di modificare le variabili non locali senza tener
conto di come esse siano usate nel programma chiamante. Per tale motivo
l'utilizzazione di un identificatore non locale una pratica pericolosa e
poco raccomandabile. Inoltre, nei linguaggi con sottoprogrammi esterni,
esistono anche altri inconvenienti: non possibile eliminare le variabili
inutili (o giudicate tali dopo un rapido esame) dallarea comune e quindi il
rischio di generare errori per l'inserzione di modifiche non previste reale
146 Capitolo quarto

se si pensa che l'eliminazione di una variabile da una dichiarazione fa


saltare la corrispondenza delle altre nell'area comune.
In funzione di queste osservazioni possiamo formulare alcuni consigli.
- meglio riservare la comunanza implicita al caso in cui il programma
principale e i suoi sottoprogrammi manipolano un gruppo di variabili
veramente comuni (cio sarebbe arbitrario per queste distinguere
argomenti reali e argomenti formali).
- quando le variabili sono in comune meglio che abbiano lo stesso nome
ovunque usate. Si eviter cos nei linguaggi con sottoprogrammi interni di
ridefinire in una procedura gli identificatori gi definiti in un blocco
esterno. Analogamente, se si utilizzano delle aree comuni, sar opportuno
mantenere sempre le stesse liste di identificatori.
- se per motivi di efficienza si sceglie il meccanismo di comunanza, si deve
documentare in modo preciso sia dove l'identificatore definito sia tutte
le unit di programma che ne fanno uso, in modo da effettuare
velocemente i controlli incrociati necessari per valutare gli effetti
complessivi che la modifica di una variabile non locale comporta.
4.3.4. L'allocazione dinamica
Tutti i sottoprogrammi sono, come abbiamo visto, unit di programma con
oggetti locali, cio validi nell'ambito ristretto dell'unit, che possono essere usati
anche da altre unit o mediante la sostituzione dei parametri o mediante un
meccanismo di comunanza. Una volta terminata l'esecuzione del sottoprogramma,
tutte le sue variabili locali diventano inaccessibili fino a quando il sottoprogramma
non viene richiamato; ma, nella chiamata successiva, i valori iniziali di queste
variabili saranno diversi da quelli finali della chiamata precedente. Questo perch
l'allocazione in memoria centrale delle variabili locali avviene all'atto della
chiamata del sottoprogramma per cercare di ridurre l'occupazione complessiva di
memoria. Difatti alcuni sottoprogrammi potrebbero non essere mai attivati, e
l'allocazione in memoria delle rispettive variabili locali risulterebbe alquanto
inutile. Risulterebbe altrettanto inutile allocare le variabili di quei sottoprogrammi
non eseguiti mai contemporaneamente. Cos, al termine di un sottoprogramma
l'area di memoria occupata dalle sue variabili locali viene resa disponibile per
l'allocazione delle variabili locali di un diverso sottoprogramma. In altre parole, la
corrispondenza tra identificatore di variabile e indirizzo del registro di memoria
viene determinata dinamicamente durante l'esecuzione.
Diremo questo schema di allocazione dinamico per distinguerlo da quello
statico che prevede che l'allocazione venga fissata all'inizio dell'esecuzione del
programma e non cambi finch il programma non sia terminato.
Nella gerarchia di unit di programma, le variabili globali (quelle definite nel
programma principale) sono statiche per ovvie ragioni ( l'unit che chiama tutte le
altre), mentre quelle locali ai vari sottoprogrammi sono dinamiche.

Per una completa comprensione degli effetti della gestione differenziata


dell'allocazione delle variabili si osservi l'output del seguente esempio:
La struttura dei programmi 147

program P;
var I,A : integer;

procedure Y;
var B: integer;
begin
A:=A+1;
B:=A*A
writeln('B =',B:2)
end;

procedure Z;
var C: integer;
begin
A:=A+1;
C:=C+1;
writeln('C =',C:2)
end;

begin
A:=0;
for i:=1 to 2
do begin
Y;
Z;
end;
writeln('A =',A:2)
end.

OUTPUT:
B=1
C=2
B=9
C =10
A=4

Lo schema che segue uno schema di principio che mostra, passo dopo passo,
come ci avvenga mettendo in evidenza l'allocazione delle variabili subito dopo la
chiamata delle procedure e poco prima della loro terminazione. I passi sono:
1) prima dell'esecuzione del ciclo for del programma principale;
I A
0
2) prima chiamata di Y;
I A B
1 0
3) terminazione di Y;
I A B
1 1 1
148 Capitolo quarto

4) prima chiamata di Z;
I A C
1 1 1
5) terminazione di Z;
I A C
1 2 2
6) seconda chiamata di Y;
I A B
2 2 2
7) terminazione di Y;
I A B
2 3 9
8) seconda chiamata di Z;
I A C
2 3 9
9) terminazione di Z.
I A C
2 4 10

Se invece tutte le variabili, senza distinguere tra locali e non, fossero state
allocate staticamente all'atto dell'attivazione del programma lo schema risulterebbe
uguale a:
I A B C

permettendo di generare il seguente output:

B= 1
C =98
B= 9
C =99
A= 4

come facilmente si pu dimostrare.


Si noti il valore strano assunto la prima volta da C. Esso si spiega osservando
che l'allocazione delle variabili associa ad esse un indirizzo di uno o pi registri di
memoria senza interessarsi minimamente del loro contenuto. Alcuni linguaggi, per
limitare i danni legati all'indeterminatezza del valore iniziale delle variabili, fanno
seguire all'allocazione una inizializzazione delle celle di memoria compatibile con
il tipo di dato. comunque buona norma non fare affidamento su tali
inizializzazioni perch possono, tra l'altro, variare da macchina a macchina, ed
inizializzare con esplicite istruzioni di assegnazione tutte le variabili che si usano.
Ma, tornando alla differenza tra allocazione statica e dinamica, resta solo da dire
che in alcuni linguaggi (ad esempio il FORTRAN) possibile rendere statiche le
variabili locali dei sottoprogrammi con opportune dichiarazioni (save).
La struttura dei programmi 149

4.3.5. La ricorsione
La ricorsione una propriet dei sottoprogrammi e delle funzioni che consente
l'uso, all'interno della definizione di un'unit di programma, della chiamata ad essa
stessa. una propriet molto importante in quanto molto problemi possono essere
definiti in modo semplice solo usando la ricorsione. La definizione di un oggetto si
dice ricorsiva se contiene un riferimento all'oggetto stesso.
Tale principio usato con naturalezza nella vita comune. Nel linguaggio
matematico numerosi oggetti sono definiti ricorsivamente, per esempio il fattoriale
di un numero n dato dal numero n moltiplicato per il fattoriale di (n-1) con
eccezione del fattoriale di 0 che uguale a 1:

0! = 1
n! = n * (n-1)!

per ogni n>0.


Non tutti i linguaggi per prevedono la ricorsione: laddove prevista si fa
ricorso ad essa inserendo nel corpo della procedura o della funzione il richiamo
alla procedura stessa. Per esempio, sempre per il fattoriale di n:

function fatt(n:integer):integer;
begin
if n=0 then fatt:=1
else fatt:=n*fatt(n-1)
end;

Comunque la ricorsione sempre riconducibile ad una iterazione, e ci fa


comprendere quanto sia importante studiare la condizione di terminazione degli
algoritmi ricorsivi.
4.3.6. Gli effetti collaterali
L'uso dei sottoprogrammi pu introdurre errori che non sono facilmente
individuabili e che sono essenzialmente legati ai meccanismi con i quali si espande
la visibilit di una informazione tra le diverse componenti di un programma. A tale
riguardo si osservi, per esempio, che la sostituzione per riferimento fa s che una
stessa variabile si presenti sotto differenti denominazioni in diverse unit di
programma. Difatti, fissata la seguente procedura:
procedure quadrato(var x:integer);
begin
x:=x*x;
end;

se la si richiama nel contesto seguente:


for i:=1 to n
do begin
.....
quadrato(i);
.....
end;
150 Capitolo quarto

si genera la modifica della variabile contatore del ciclo, operazione pericolosa se


non addirittura vietata in alcuni linguaggi, in quanto, per effetto del meccanismo di
sostituzione adottato, le operazioni svolte dal sottoprogramma su x si svolgono di
fatto su i.
Chiameremo side effect (effetto collaterale) l'effetto non desiderato che si
genera a causa della modifica di un parametro formale nel corpo di un
sottoprogramma che si ripercuote anche all'interno del programma chiamante.
Un'altra fonte di effetto collaterale l'uso della sostituzione per riferimento
per uno dei parametri formali di una funzione. Difatti se il parametro in questione
viene modificato nel corpo della funzione si ha come effetto collaterale che la
procedura genera pi di un risultato contravvenendo alla definizione stessa di
funzione. Per esempio la funzione:

function radice(var x:real):real;


begin
x:=abs(x);
radice:=sqrt(x)
end;

che effettua il calcolo della radice quadrata del valore assoluto di un numero
assegnato, fornisce due risultati: il valore assoluto del numero, tramite l'unico
parametro formale, e il valore della radice, attraverso il nome della funzione. In
casi del genere si pu facilmente ovviare all'effetto collaterale sostituendo il
meccanismo di sostituzione per riferimento con quello per valore. Alcune volte
per, si fa volutamente ricorso al meccanismo di sostituzione per riferimento sia
per motivi di efficienza (si evita la ricopiatura) che per non aumentare
l'occupazione di memoria (non si duplicano le informazioni). Si pensi ad esempio
ad un sottoprogramma per il calcolo del determinante di una matrice quadra: la
scelta della sostituzione per valore comporterebbe la copia della matrice nel
parametro formale con una perdita di tempo e di spazio proporzionale alle
dimensioni di tutta la matrice. La scelta della sostituzione per riferimento laddove
dovrebbe essere usata quella per valore, pu essere fatta solo quando, a progetto
terminato, ci si accorge che il sottoprogramma non opera sul parametro formale e,
quindi, non introduce degli effetti collaterali non desiderati.
Per la loro pericolosit, tali scelte vanno documentate in modo chiaro ed
evidente nel corpo del sottoprogramma soprattutto perch un programma non cessa
di essere utilizzato nello stesso stato in cui stato creato la prima volta. Difatti
durante la sua vita molto probabile che dovr subire diverse modifiche sia per
rimuovere eventuali errori di programmazione sia per adattarsi a cambiamenti delle
specifiche iniziali.
A questo riguardo si pensi ad un semplice calcolo della ritenuta IVA su una
fattura: fino a qualche anno fa l'aliquota da applicare era del 18 per cento mentre
oggi del 19 per cento. Intervenendo allora su di un sottoprogramma in cui non
sono state documentate le ipotesi di correttezza, si rischia di alterarne i rapporti con
l'esterno introducendo effetti collaterali che nella versione di partenza non
esistevano.
La struttura dei programmi 151

4.3.7. Il riuso dei sottoprogrammi


In genere quanto maggiore lo sforzo investito nella realizzazione di un
programma, tanto maggiore sar il desiderio da parte di chi lo ha realizzato di
prolungarne la vita e quindi maggiore sar la probabilit di intervenire per
correggerlo o modificarlo. Tale attivit prende il nome di manutenzione e, ormai,
opinione diffusa che la manutenzione di un grande progetto software un
problema addirittura pi complesso e costoso della sua realizzazione. Senza entrare
nel merito di tale affermazione, diciamo soltanto che l'unico modo per diminuire la
complessit della manutenzione progettare un software ben organizzato e
documentato: i sottoprogrammi sono un utile meccanismo per arrivare ad un
progetto con questi requisiti sia perch sono un potente strumento di astrazione sia
perch godono di una certa autonomia che ne consente l'organizzazione in librerie
utilizzabili in progetti diversi. L'autonomia di fondamentale importanza non solo
per la riutilizzabilit dei programmi, ma anche per ridurre i tempi di sviluppo e
quelli di manutenzione di un progetto software. Difatti, per quanto potenti oggi
siano i calcolatori attuali, la compilazione di programmi molto lunghi pu risultare
assai onerosa e quindi lenta e costosa.
Per questo motivo alcuni linguaggi introducono il concetto di sottoprogrammi
esterni, ossia di sottoprogrammi compilabili separatamente dal loro contesto. Cos,
durante lo sviluppo di un progetto, si compilano soltanto i moduli nuovi o quelli
che sono stati oggetto di modifiche, con un notevole risparmio di tempo. Inoltre
l'autonomia favorisce la manutenzione in quanto permette di verificare
separatamente la correttezza di ogni singolo modulo e successivamente quella del
programma che li integra.
Ma l'autonomia di un sottoprogramma non per illimitata. Gli effetti
collaterali precedentemente illustrati ne sono un esempio. In molti casi un
sottoprogramma risulta completamente definito solo se immerso nel contesto che
lo usa. Tanto per fare un ulteriore esempio, si pensi ad un sottoprogramma i cui
calcoli dipendano da una (o anche pi di una) variabile non locale in un ambiente
come il Pascal o da una variabile inserita in area comune per ambienti con
sottoprogrammi di tipo esterno: per provare la correttezza del sottoprogramma si
deve disporre anche della parte di programma che d valore alla variabile prima
della chiamata del sottoprogramma stesso.
Per favorire quindi l'autonomia dei sottoprogrammi auspicabile ridurre l'uso
di meccanismi che possono introdurre effetti collaterali o quantomeno giustificarne
l'adozione, con una dettagliata documentazione, in tutti quei casi in cui si ottenga
una stesura pi semplice del programma.
4.3.8. L'information hiding
Un altro modo di aumentare l'autonomia dei sottoprogrammi cercare di non
usare informazioni al di fuori dei moduli che sono stati creati apposta per trattarli.
In altre parole, si deve cercare di nascondere le informazioni (dall'inglese
information hiding) che vengono usate a certi livelli di astrazione del metodo top
down ai livelli superiori, se si vuole aumentare l'indipendenza dei singoli moduli.
In altri termini lintestazione di un sottoprogramma deve contenere tutto quanto
serve alla comprensione di come esso pu essere usato. Per comprendere
l'importanza del problema si consideri il rapporto che esiste tra i sottoprogrammi e
152 Capitolo quarto

i tipi astratti. Si visto che non sempre il linguaggio mette a disposizione del
programmatore strumenti che permettono di modellare gli oggetti della realt, e ci
non deve rappresentare un impedimento durante la progettazione. Difatti, se una
informazione essenzialmente caratterizzata da struttura ed operazioni, si pu
sempre raffinare il tipo astratto usando una struttura che si presta ad una sua
realizzazione e costruendo intorno ad essa funzioni e sottoprogrammi che
arricchiscono il linguaggio con parole chiavi tali da dare la sensazione di disporre
proprio di un tale tipo. Per esempio un numero complesso pu essere raffinato
introducendo il seguente tipo:

type num_complex = record


parte_reale,
parte_immaginaria : real
end;

e le seguenti operazioni:

function somma_complex(x,y:num_complex):num_complex;
function prodotto_complex(x,y:num_complex):num_complex;

noto che i numeri complessi possono essere rappresentati in modo


alternativo indicando modulo ed argomento (un angolo). Cos, se per qualche
motivo si sceglie di cambiare la rappresentazione del tipo, si potranno modificare
solo la dichiarazione di tipo e il corpo delle funzioni somma_complex e
prodotto_complex nel caso in cui non siano state disseminate all'interno del
programma istruzioni del tipo:

if x.parte_reale then
z:=num.parte_reale*3

ossia se stata nascosta la struttura del tipo num_complex al programma


stesso.
Si noti che nessun linguaggio costringe al rispetto del principio
dellinformation hiding all'interno di un programma. Di conseguenza compito del
buon progettista cercare di disciplinarsi o disciplinare gli altri quando il progetto
condotto da pi persone, se vuole ottenere un progetto in cui tutti moduli godano di
quella reale autonomia cos fondamentale per un pi semplice controllo.
Capitolo quinto

I dati

5.1. Informazione e dato


Nella realt di tutti i giorni molte attivit operano con informazioni di natura e
forma diverse (testo, video, audio) e si costruiscono sistemi che gestiscono ed
elaborano tale tipologie di informazioni.
Linformazione quindi un oggetto che ha un rapporto stretto con la realt
dalla quale pu emergere se e solo se un determinato insieme o classe di oggetti
assume stati o configurazioni differenti. In tale accezione linformazione non
altro che la scelta di una delle possibili configurazioni in cui si trova un esemplare
della classe di oggetti. Allora, il concetto di informazione strettamente legato a
quello di scelta di uno fra pi oggetti di un particolare insieme e non esiste
informazione se non si effettua una scelta. Ad esempio, la frase sto studiando
elementi di informatica, fornisce un'informazione in quanto esprime la scelta della
materia di studio e l'identificazione, quindi, della materia elementi di
informatica, tra tutte le possibili materie del piano di studio.
Mentre luomo tratta informazioni, lelaboratore tratta dati. Come le
informazioni anche i dati che le rappresentano sono di tipo numerico,
alfanumerico, alfabetico, immagine, audio. Quando lelaborazione consente di
trattare dati eterogenei in modo integrato si parla di elaborazione multimediale.
Con dato si indica una rappresentazione di fatti e concetti in modo formale perch
sia possibile una loro elaborazione da parte di strumenti automatici. Il dato da solo,
senza un contesto, pu non avere significato: uno stesso numero pu esprimere
cose diverse in situazioni diverse; cos come una stessa parola pu avere significato
diverso dipendente dal contesto. Lambiguit viene risolta dando al dato una
interpretazione. Linformazione non altro che la percezione del dato attraverso un
processo di interpretazione. In altre parole linformazione cattura il significato del
dato. Perch un dato diventi informazione si devono pertanto specificare:
- un tipo, ossia l'insieme degli elementi in cui si effettua la scelta;
- un valore, ossia il particolare elemento scelto;
- l'attributo, ossia l'identificatore (o anche metadato) che conferisce
significato all'elemento scelto.
Il tipo l'insieme degli oggetti entro cui si effettua la scelta. Nella prassi
quotidiana tale insieme non sempre esplicitamente definito. Tra laltro
l'attribuzione di un dato ad un tipo piuttosto che ad un altro dipende, dal contesto:
154 Capitolo quinto

ad esempio, pu non avere interesse in alcuni casi trattare separatamente con i tipi
giorno, mese ed anno piuttosto che con un unico tipo data che li accorpa.
Ciascun tipo di dato caratterizzato poi dal numero di elementi di cui
composto. Tale numero detto cardinalit del tipo. In un tipo con cardinalit N, la
scelta di un elemento avviene tra tutti gli N; ne discende che la scelta pi
elementare che si possa effettuare quella fra due soli oggetti e pertanto il dato pi
elementare quello che appartiene ad un tipo con cardinalit 2. Un tale dato viene
detto binario se i valori di un tale insieme sono [0,1], ma anche vero e falso; e si
soliti usare il termine bit per indicare sia l'informazione binaria sia i valori 0 e 1 da
essa assunti.
Da un punto di vista teorico la cardinalit di un tipo pu essere finita o
infinita. Da un punto di vista pratico i processi elaborativi trattano solo tipi a
cardinalit finita. Difatti, per poter essere gestiti da un elaboratore, i dati,
opportunamente rappresentati, devono essere inseriti in contenitori (memoria
dell'elaboratore) con dimensioni proporzionali alla cardinalit dei tipi: chiaro che
a cardinalit infinita corrisponderebbero contenitori di dimensione infinita.
Il valore di un dato un elemento di un tipo. Tale definizione risulta per
generica se non viene collegata alla definizione di tipo. Si osservi che il concetto di
valore un concetto che comporta una certa astrazione e non deve essere confuso
con il problema della rappresentazione dell'informazione che pu essere diversa in
contesti differenti: basti pensare al modo di identificare un oggetto in lingue
diverse (padre e father) o alle differenti rappresentazioni dei valori dei numeri
(1/10 o 0.1).
L'attributo conferisce il significato al valore specificato dal dato. Il tipo e il
valore da soli non sono sufficienti alla caratterizzazione di una informazione. Ad
esempio il valore 1000 del tipo numeri interi non ci dice nulla di significativo:
potrebbe benissimo essere il prezzo di un biglietto del tram o anche il numero di
matricola di uno studente. Il concetto di attributo analogo a quello matematico di
variabile e, come questo, si pu rappresentare mediante simboli letterali o nomi da
associare alle informazioni per specificarne il significato. Si pu cos chiamare rad
la radice di una equazione e totale_fattura l'ammontare di una fattura.
L'individuazione dei tipi di dato con cui trattare strettamente legata alla
natura delle elaborazioni da effettuare e pu variare dunque da elaborazione ad
elaborazione. La definizione di tipo consente di introdurre dei modelli degli oggetti
della realt in quanto ne fornisce le caratteristiche strutturali (cosa sono) e
funzionali (a cosa servono). Una definizione di tipo di dato sufficientemente
rigorosa e particolarmente adatta agli usi di tipo che verranno fatti in seguito la
seguente: per tipo di dato si intende un insieme di valori associati a delle
operazioni definite su di essi. Con le definizioni di tipo possibile trattare
l'informazione in maniera astratta, cio prescindendo dal modo concreto con cui
essa rappresentata all'interno di una macchina.
Un tipo si definisce alla stessa stregua degli insiemi, ossia per:
1) enumerazione degli elementi
{Lunedi, Martedi, Mercoledi, Giovedi, Venerdi, Sabato, Domenica}
2) elencando le propriet di cui godono i suoi elementi
{C: centravanti di una squadra nazionale di calcio di serie A e B}
Elementi fondamentali di una elaborazione non sono soltanto i tipi su cui essa
agisce, ma anche le operazioni che si possono effettuare sui rispettivi valori di un
I dati 155

tipo. Tali operazioni possono essere definite assiomaticamente con il tipo e


possono essere interne oppure esterne ad esso. Le prime producono un risultato di
tipo uguale a quello degli operandi mentre le seconde di tipo diverso. Ad esempio
la somma di due numeri interi di tipo interno mentre esterna la divisione tra due
numeri interi in quanto fornisce sia risultati di tipo intero (8:4 => 2) che di tipo
reale (5:2 => 2.5).
Fra i valori di un tipo pu essere definita una relazione d'ordine che fissa che
uno specifico valore ha alcuni altri valori che lo precedono e altri ancora che lo
seguono: la relazione dordine pu essere totale (se coinvolge tutti gli elementi) o
in alcuni casi parziale (se definita su sottoinsiemi). Nella pratica tale relazione
implicita nella natura del tipo e corrisponde alla posizione che le sue costanti
occupano nella sua definizione per enumerazione. Sono ad esempio ordinati gli
insiemi dei numeri interi, delle lettere e delle cifre. I tipi ordinati sono anche detti
scalari.
Tra due costanti di un tipo ordinato sono definite a priori, oltre ai confronti di
uguaglianza e disuguaglianza, validi anche per tipi non ordinati, le relazioni
d'ordine di minore, maggiore, minore o uguale e maggiore o uguale. Le diverse
relazioni vengono indicati con opportuni operatori (vedi tabella 1).

Confronto Operatore Operatore


uguale = ==
diverso <> !=
minore < <
maggiore > >
minore o uguale <= <=
maggiore o uguale >= >=
Tabella 1 - Relazioni su tipi ordinati

Tra le costanti di un tipo sono possibili operazioni di relazione (confronto):


operazioni, cio, che trasformano valori di un qualsiasi tipo nei valori logici VERO
e FALSO (e pertanto sono operazioni esterne).

Confronto Relazione Valore determinato


3>1 Verificata VERO
7=9 Non verificata FALSO
Tabella 2 - Operazioni di confronto

Infine, sui tipi ordinati sono definite le seguenti funzioni:


- SUCC(x) che fornisce il successore di x nell'insieme;
- PRED(x) che fornisce il predecessore di x nell'insieme;
- ORD(x) che fornisce la posizione di x nell'insieme (la prima posizione si
assume per convenzione essere uguale a 0).
Si noti che le funzioni succ e pred sono interne al tipo a cui sono applicate,
mentre ord esterna perch fornisce una posizione intera qualsiasi sia il tipo di x.
Ad esempio, fissato l'insieme delle lettere, si ha che SUCC(A)=B, PRED(B)=A,
ORD(A)=0 e ORD(B)=1. Le prime due sono operazioni interne mentre le ultime due
sono esterne.
156 Capitolo quinto

5.2. La classificazione dei tipi


Nei linguaggi di programmazione esistono dichiarazioni per la definizione di tipo:

type t = T;

dove t il nome del nuovo tipo e T un descrittore di tipo, cio un costrutto


sintattico del linguaggio atto a definire il tipo t. Il descrittore T pu essere anche un
tipo gi definito o predefinito nel linguaggio, in modo da introdurre dei tipi
sinonimi. L'istanziazione di variabile, ossia lintroduzione di una variabile da poter
utilizzare nel programma, avviene poi semplicemente con frasi dichiarative come
quella seguente:

var v : T;

Allinstanziazione di variabile corrisponde lallocazione in memoria della


variabile, ossia lassociazione al nome della variabile dellindirizzo del registro di
memoria nel quale verranno depositati i valori della variabile e da dove gli stessi
valori verranno letti. Il numero di bit, e quindi il numero di registri associati al
nome della variabile saranno determinati sulla base della rappresentazione dei
valori in memoria.
I tipi di dato si possono classificare in funzione della modalit con la quale
possibile definirli o in funzione della loro struttura. Se il tipo predefinito nel
linguaggio, allora lo si dir primitivo: in caso contrario, quando il programmatore
che ne deve definire la composizione e in alcuni casi anche le operazioni ammesse
sui suoi valori, si dir che un tipo derivato. Tutti i linguaggi di programmazione
comprendono nella loro grammatica dei costruttori di tipo.
Un tipo si dice atomico quando i valori di cui si compone non possono essere
scomposti in elementi pi semplici; si dice invece strutturato quando presenta
valori che sono aggregazioni di altri valori che possono a loro volta essere semplici
o strutturati. Il colore di un oggetto una informazione di tipo atomico; la data di
nascita invece strutturata perch in essa si ritrovano aggregati le informazioni del
giorno, del mese e dellanno.
La classificazione di un tipo in atomico e strutturato dipende fortemente
dalluso dellinformazione. La stessa informazione per alcune elaborazioni pu
essere considerata atomica mentre per altre strutturata. Ad esempio, se si considera
un libro, si pu notare che per il libraio che lo deve vendere un oggetto di tipo
atomico, ma per tante altre persone (il lettore, il tipografo, leditore) pu essere
visto organizzato in pagine, le pagine in righe, le righe in parole e le parole in
caratteri, a secondo dello specifico punto di vista.
Sono tipi atomici:
- il tipo logico o booleano
- i tipi numerici intero e reale
- il tipo carattere
- il tipo puntatore
sono tipi strutturati:
- il tipo array ad una o a pi dimensioni
- il tipo record
I dati 157

- il tipo file
- la pila o stack
- la coda
- la tabella
In tutti i linguaggi di programmazione esistono i costruttori per definire array,
record e file.

5.3. I tipi atomici


I tipi si dicono atomici quando i valori di cui sono composti non sono
scomponibili in elementi pi semplici dalle applicazioni che ne fanno uso. Per tale
motivo facile fornire una loro definizione mediante lenumerazione delle costanti
appartenenti al tipo o definendo le propriet di cui le stesse costanti godono. Tra le
propriet vanno considerate le operazioni interne ed esterne al tipo.
5.3.1. Il tipo booleano
Il tipo booleano indica l'insieme dei due valori di verit, true e false, ed il suo
nome deriva dall'ideatore del calcolo logico, George Boole (1915-1964). Sui valori
del tipo logico sono definiti i seguenti operatori:

- OR: equivalente alla disgiunzione inclusiva (O logico), anche detto


somma logica;

non si fa la lezione se
il docente malato
O
gli studenti non vengono

- AND: equivalente alla congiunzione (E logico), anche detto prodotto


logico;

l'esame si supera se
si fa bene l'esercizio
E
si risponde bene all'orale

- NOT: equivalente alla negazione (NON logico).

oggi NON piove

La tabella 3 riporta come gli operatori logici vengono indicati in diversi


linguaggi.

Operazione Operatore
OR ||
AND &&
NOT !
Tabella 3 - Operatori logici
158 Capitolo quinto

Per capire gli operatori logici definiamo proposizione una espressione


linguistica della quale si pu dire se vera o falsa. Ad esempio Parigi la capitale
della Francia una frase che universalmente riconosciuta essere vera. Invece
definiamo proposizioni variabili o predicati le espressioni linguistiche delle quali
si pu dire che sono vere solo se si assegna un valore alle informazioni in esse
contenute. Ad esempio per la frase:
oggi piove
possibile pronunciarsi solo dopo aver osservato il cielo. Si dice valutazione
del predicato l'operazione che, noti i valori delle informazioni che vi compaiono,
consente di stabilirne il valore di verit (cio di dire se il predicato vero o falso).
Allora dato un predicato P se ne pu costruire un altro facendolo precedere dal
simbolo NOT: il predicato che si ottiene si dice opposto o negazione del primo ed
ha i valori di verit opposti rispetto al primo. Cio se P vero allora NOT P falso
e viceversa. Si noti che ovviamente che: NOT(NOT P1) = P1.
Dati invece due predicati P1 e P2, se ne pu costruire un terzo mediante la
congiunzione logica AND. Il predicato ottenuto P1 AND P2 risulter vero solo
quando sia P1 che P2, sono veri mentre sar falso in tutti gli altri casi.
Infine, dati due predicati P1 e P2, se ne pu costruire un terzo mediante la
disgiunzione logica O. Il predicato ottenuto P1 OR P2 risulter falso solo quando
sia P1 che P2, sono falsi mentre sar vero in tutti gli altri casi.
Dalle definizioni date possibile riempire le seguenti tabelle che vengono
dette tavole di verit (la prima utilizzando false e true, la seconda 0 e 1).

P1 P2 P1 AND P2 P1 OR P2 NOT P1
FALSE FALSE FALSE FALSE TRUE
FALSE TRUE FALSE TRUE
TRUE FALSE FALSE TRUE FALSE
TRUE TRUE TRUE TRUE

P1 P2 P1 AND P2 P1 OR P2 NOT P1
0 0 0 0 1
0 1 0 1
1 0 0 1 0
1 1 1 1

Tabelle 4,5 Tavole di verit


Inoltre valgono le seguenti propriet

commutativa:: X OR Y = Y OR X

associativa: (X OR Y) AND Z=(X AND Z) OR (Y AND Z)

nonch le due relazioni di DE MORGAN:

NOT (X OR Y)=(NOT X) AND (NOT Y)


NOT (X AND Y)=(NOT X) OR (NOT Y)
I dati 159

che si dimostrano con le tavole di verit in tabella 6.

X Y X OR Y NOT (X OR Y) NOT X NOT Y (NOT X) AND


(NOT Y)
FALSE FALSE FALSE TRUE TRUE TRUE TRUE
FALSE TRUE TRUE FALSE TRUE FALSE FALSE
TRUE FALSE TRUE FALSE FALSE TRUE FALSE
TRUE TRUE TRUE FALSE FALSE FALSE FALSE
Tabella 6 Tavole di verit

Inoltre, si noti che gli operatori di relazione, per definizione stessa di


confronto, trasformano due o pi valori di un tipo in un valore del tipo logico.
Difatti A confronto con B assume il valore TRUE se il confronto ha successo e il
valore FALSE in caso contrario.
Si noti che linsieme degli operatori di relazione riconducibile a due soli
operatori di relazione (l'uguale e il minore ad esempio) e ai tre operatori logici.
Operazione Equivalenza
X<>Y NOT (X = Y)
X<=Y (X < Y) OR (X = Y)
X>=Y NOT (X < Y)
X>Y NOT ((X < Y) OR (X = Y))
Tabella 6 Equivalenza tra operatori

Se in una espressione logica sono presenti operatori logici e di relazione e


sono assenti le parentesi devono essere eseguite nell'ordine il NOT, l'AND, l'OR, e
infine gli operatori di relazione.
5.3.2. Il tipo carattere
Il tipo carattere un tipo di fondamentale importanza in quanto contiene
l'insieme dei simboli medianti i quali un calcolatore comunica con l'esterno
attraverso i dispositivi di ingresso e uscita. Se si considera come unione degli
insiemi delle lettere, delle cifre e dei simboli di interpunzione, matematici e
speciali, allora si presenta come parzialmente ordinato poich non ha senso definire
un ordinamento tra le lettere e le cifre o tra i caratteri di interpunzione. Da un punto
di vista pratico, per, si presenta come ordinato.
Inoltre, sempre per motivi pratici, legati alla necessit di differenziazione in
fase di visualizzazione, nell'insieme dei caratteri si ritrovano sia le lettere
minuscole che maiuscole. I motivi che hanno condotto all'ordinamento del tipo
carattere sono legati alla necessit di definire un insieme di caratteri che fosse
accettato da tutte le case costruttrici di computer e di periferiche quali stampanti e
video. Difatti solo definendo un insieme standard di caratteri stato possibile far
comunicare sistemi diversi tra loro o adattare periferiche di una casa a sistemi di
altre case.
Lo standard ormai universalmente accettato quello definito dall'American
Standard Code for Information Interchange (ASCII). Si basa su 128 codici di cui i
primi trentadue vengono anche chiamati Control per la loro caratteristica di servire
come comandi di gestione delle periferiche. Ai codici di controllo non corrisponde
160 Capitolo quinto

un simbolo visibile, ma l'esecuzione di un comando da parte della periferica che lo


riceve. Di essi ricordiamo:
- Cr (detto anche RETURN o anche IMMISS) che serve al computer per
comunicare alla stampante o al video di spostare l'elemento di stampa ad
inizio rigo (la levetta di una macchina da scrivere per andare daccapo);
- Lf o Line Feed, che serve al computer per comunicare alla stampante o al
video di spostare l'elemento di stampa sul rigo seguente;
- BLANK o Space per introdurre spazi di separazione tra le parole.
L'ASCII introduce un ordinamento tra i simboli che funzione della loro
posizione nella tabella di codifica. Per tale ordinamento:
- le cifre precedono le lettere;
- le lettere maiuscole precedono quelle minuscole.
Sul tipo carattere ordinato sono solitamente definite le funzioni (ad esempio
CHR e ORD) che pongono in corrispondenza biunivoca l'insieme dei simboli con la
loro posizione all'interno del tipo. In particolare la funzione CHR restituisce il
simbolo della posizione specificata e, viceversa, la ORD restituisce la posizione di
un dato simbolo (le posizioni partono da zero e arrivano a 127). Ad esempio,
osservando la tabella ASCII si pu verificare che ORD(B)=66 e CHR(65)=A;
ovviamente CHR(ORD(A))=A e ORD(CHR(66))=66.
5.3.3. Il tipo intero
Il tipo intero (o integer) si definisce come un sottoinsieme finito e definito
dell'insieme dei numeri interi:
type integer = { n | n appartiene [- m, M] }
Spesso si ha che m = M. In questo caso tale valore viene detto MAXINT e si ha
che i valori rappresentabili dal tipo intero sono compresi nell'intervallo:
[-MAXINT,MAXINT]
Le costanti del tipo intero sono sequenze di cifre eventualmente precedute da
un segno. Ad esempio sono numeri interi 30000, +400, 500, -678 e -1. La
tabella che segue indica le operazioni definite sul tipo intero e gli operatori
solitamente usati in diversi linguaggi per indicarle. Nella tabella x e y sono due
variabili di tipo intero.
Operazione Operatore Operatore Esempi
Somma + + x+y
Sottrazione - - xy
Moltiplicazione * * x*y
Divisione (esterna) / / x/y
Divisione intera DIV / x DIV y
x/y
Resto MOD % x MOD y
x%y
Tabella 7 Operatori su tipo intero
Si noti che se si definisce insieme di overflow l'insieme dei valori interi tali
che:
x : |x| > MAXINT
I dati 161

(ossia l'insieme dei numeri interi esterni al tipo intero), si ha che le operazioni
elementari della aritmetica applicati a valori del TIPO INTERO valgono se e solo
se (a op b) non assume valori nell'insieme di overflow.
Ossia se e solo se:

| a op b | MAXINT

dove op = {+,-,*},
In pratica la condizione di overflow pu tradursi in vario modo all'interno di
una macchina: per esempio pu generare una condizione di errore con interruzione
del programma.
5.3.4. Il tipo reale
Il tipo reale ( o real) diverso dall'insieme dei numeri reali poich in un
intervallo reale comunque piccolo esistono infiniti valori (i numeri reali formano
un continuo). Allora il tipo reale si definisce come l'insieme che contiene un
numero finito di valori reali ognuno dei quali rappresenta un intervallo del
continuo. In altri termini, diviso l'insieme dei numeri reali in intervalli di fissata
dimensione, si ha, come la figura mostra, che ogni x appartenente all'intervallo
[Xi,Xi+1[ viene sostituito con Xi.

Figura 1 Il tipo reale

La sostituzione di un numero reale x con il valore X rappresentante l'intervallo


a cui x appartiene, pone notevoli problemi di approssimazione in tutti i calcoli che
usano valori del tipo reale.
Il tipo reale si definisce come un sottoinsieme DISCRETO dei numeri reali. In
concreto il tipo reale si definisce come l'insieme dei numeri reali che godono della
seguente propriet:
162 Capitolo quinto

x X
X:
X
dove rappresenta l'errore relativo che si commette sostituendo x con X. dipende
dalla rappresentazione finita (numero finito di cifre) utilizzata per i numeri reali.
Ad esempio disponendo di una calcolatrice con una aritmetica a quattro cifre
decimali che applica le note regole di arrotondamento sull'ultima cifra, si hanno gli
errori di rappresentazione in tabella 8.

NUMERO ARROTONDAMENTO ERRORE


0.00347 0.0035 3*10-5 =0.3 *10-4
0.000347 0.0003 47*10-6 =0.47*10-4
0.00987 0.0099 3*10-5 =0.3 *10-4
0.000987 0.0010 13*10-6 =0.13*10-4
Tabella 8 Errori di rappresentazione

In generale se n sono le cifre decimali l'errore che si commette :


1 n
10
2

Per il tipo reale, si definisce insieme di overflow l'insieme costituito da tutti i


valori:
x non appartenenti a [-m , M ]

ossia dei valori pi grandi di quelli rappresentabili; e insieme di underflow


quello costituito da tutti i valori:
x appartenenti a ]- , [ tali che X = 0

ossia l'insieme di valori che vengono confusi con lo zero.


Le operazioni definite sul tipo reale sono la moltiplicazione (*), la divisione
(/), laddizione (+) e la sottrazione (-). Le operazioni che richiedono maggiore
attenzione sono l'addizione e la sottrazione. Infatti, la causa principale degli errori
di calcolo numerico risiede nella sottrazione di numeri di valore quasi uguale; in tal
caso le cifre pi significative si eliminano fra loro e la differenza risultante perde
un certo numero di cifre significative o anche tutte (fenomeno detto cancellazione).
Altra causa di errori la divisione per valori molto piccoli, poich il risultato pu
facilmente superare il valore di overflow. Quindi deve essere evitata non solo la
divisione per lo zero ma anche per valori ad esso prossimi.
Nei linguaggi di programmazione il tipo intero e il tipo reale sono due tipi
distinti, anche se il primo un sottoinsieme del secondo, in quanto le operazioni
sui numeri interi forniscono risultati esatti e sono pi veloci. Comunque, si
conviene definire funzioni di conversione esplicite ed implicite tra i due tipi. In una
espressione con valori reali, un qualsiasi operando intero viene implicitamente
convertito in reale. Viceversa le funzioni di troncamento e arrotondamento (ad
esempio trunc e round) consentono la conversione esplicita da reale ad intero. La
funzione trunc(X) restituisce il numero intero che si ottiene troncando le cifre
frazionarie di X. La funzione round(X) arrotonda X troncando il valore dopo avergli
aggiunto 0.5.
I dati 163

5.3.5. Il tipo per enumerazione


Il tipo per enumerazione un tipo di utente in quanto viene definito
elencando l'insieme dei valori che lo costituiscono. Ad esempio:

type GIORNO = (lun,mar,mer,gio,ven,sab,dom)


type SESSO = (maschio,femmina)

Anche se a priori una raccolta disordinata di valori, solitamente si conviene


di definire un ordinamento al suo interno in funzione dell'ordine di apparizione
delle costanti nella definizione di tipo. Per tale motivo:

lun<mar<mer<gio<ven<sab<dom
maschio<femmina

In questo modo, per le costanti di un tipo per enumerazione sono definite le


funzioni PRED(), SUCC() e ORD() ed possibile applicare ad esse tutti gli operatori
di relazione.
5.3.6. Il tipo subrange
Il tipo subrange si definisce specificando un sottoinsieme di un tipo
predefinito ed ordinato e per questo motivo un tipo di utente. La costruzione del
tipo avviene specificando due estremi di un tipo ordinato (ossia i tipi integer, char,
per enumerazione con esclusione del tipo real) separati da una coppia di punti
come mostrato di seguito:

type FERIALE = lun..ven

type CODICI_MESE = 1..12

La definizione corretta se e solo se


limite inferiore < limite superiore

Il tipo cos generato conterr tutti gli elementi compresi tra i due estremi
(estremi inclusi) ed erediter le operazioni del tipo genitore (anche detto base), cio
quello di cui si scelto l'intervallo.
L'introduzione del tipo subrange importante perch aumenta la chiarezza
del modo in cui analizzata la realt del problema. Difatti in taluni casi un tipo
contiene un numero eccessivo di valori per rappresentare alcune informazioni. Ad
esempio, se risulta naturale rappresentare l'informazione anno come un intero (i
numeri negativi potrebbero servire per gli anni prima di Cristo), eccessivo
attribuire all'informazione giorno tutti i valori dello stesso tipo. Allora definire:
type giorno = 1..31
consente di introdurre dei vincoli sulle informazioni rappresentanti i giorni, vincoli
che si concretizzano in una serie di controlli introdotti implicitamente dal
traduttore del linguaggio che vietano l'assunzione errata di valori esterni
all'intervallo di definizione.
164 Capitolo quinto

5.4. I tipi strutturati


Si dice che un'informazione di tipo strutturato se composta da altre
informazioni pi semplici. Quindi un tipo strutturato se un'aggregazione di
informazioni. I tipi strutturati sono di fondamentale importanza perch tramite essi
vengono gestite aggregazioni di informazioni della realt individuate da un unico
attributo. Ad esempio, la data indica il giorno, il mese, l'anno; un mazzo di carte
indica la prima carta, la seconda, e cos via fino all'ultima carta.
Le strutture di informazioni che possibile osservare si presentano in modi
diversi e gli elementi caratterizzanti sono fondamentalmente:
- il tipo degli elementi che compongono la struttura;
- il modo in cui vengono aggregati;
- le operazioni che consentono di estrarre una componente o insiemi di
componenti da tutta la struttura;
- le operazioni che si possono fare su tutta la struttura.
Per tali ragioni un tipo strutturato si pu definire:
1. specificando il tipo delle informazioni componenti (si noti che i
componenti del tipo strutturato possono essere sia atomici che, a loro
volta, strutturati creando cos strutture di strutture);
2. indicando il costruttore necessario alla creazione della struttura, ossia le
operazioni da fare sui componenti per formare il tipo strutturato;
3. indicando il selettore (anche detto funzione di accesso) necessario alla
estrazione di un componente dalla struttura, ossia le operazioni che sul
tipo strutturato possono essere fatte per individuare uno specifico
componente della struttura
4. elencando le operazioni che sull'informazione di tipo strutturato
possono essere fatte; operazioni definite o assiomaticamente sul tipo o
mediante operazioni sui valori componenti la struttura (quasi sempre
assegnazione di valori e test di uguaglianza).
Da quanto detto ne consegue che i tipi strutturati sono tipi di utente in quanto
l'utente che ne specifica i componenti, mentre i linguaggi di programmazione
mettono a disposizione i costruttori e i selettori.
Il prodotto cartesiano e la sequenza sono i costruttori pi importanti.
Assegnati gli insiemi A e B (distinti o non), si dice prodotto cartesiano di A per B,
l'insieme costituito da tutte le coppie:

(a,b) con a appartenente ad A e b a B

Ad esempio, assegnati gli insiemi

A {1,2,3} e B {4,5}
si ha che il prodotto cartesiano di AxB :

{(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)}

Il prodotto cartesiano tra n insiemi (I1xI2xxIn) produce una ennupla


(v1,v2,vn) con v1 appartenente a I1, v2 ad I2 e vn ad In.
I dati 165

Si riportano qui di seguito alcuni esempi pratici.


- Le coordinate dei punti del piano sono, detto R l'insieme dei numeri reali,
il prodotto cartesiano RxR, per cui un punto P individuato dalla coppia
(x,y) con x e y appartenenti a R: x detta ascissa mentre y ordinata del
punto.
- Detto I l'insieme dei numeri interi e M l'insieme dei nomi dei mesi, una
data rappresentata dal prodotto cartesiano I x M x I secondo la seguente
convenzione DATA=(giorno,mese,anno). Esempi di date sono
(02,gennaio,1954), (10,dicembre,2000).
- Definito il tipo carta come l'insieme dei quattro semi {C,Q,F,P} (ossia
Cuori, Quadri, Fiori Picche), un mazzo di carte francesi il prodotto
cartesiano: Carta1 x Carta2 x ... x Carta52. Un mazzo pu essere
descritto come (prima carta, seconda carta, ... , cinquantaduesima carta) e
possibili valori sono (C,C,F,F,P,...,Q), (F,F,P,Q,Q,...,F).
Si osservi che per quanto riguarda gli insiemi che compongono il prodotto
cartesiano si ha che:
- il loro tipo pu essere distinto o uguale;
- il loro numero definito e finito;
- i loro attributi possono variare o meno a seconda della posizione
all'interno della ennupla, e nel caso in cui sono tutti uguali, la
individuazione di un elemento fatta attraverso la sua posizione.
La sequenza un costruttore che genera successioni (anche dette stringhe
ordinate) di elementi tutti dello stesso tipo e in numero non definito a priori. Le
sequenze hanno quindi una lunghezza K che pu assumere valori maggiori od
uguali a zero. Se la sequenza ha lunghezza nulla si parla allora di sequenza vuota o
stringa nulla.
Sul piano pratico non possibile avere stringhe a lunghezza infinita: pertanto
si ha una limitazione superiore K < MAX con MAX che dipende dall'ambiente e dal
tipo costruito con la sequenza.
Sulle sequenze sempre definita l'operazione di concatenazione di due o pi
stringhe: nel caso di due stringhe la concatenazione definita come l'accodamento
della seconda alla prima, mentre nel caso di pi stringhe si definisce come
l'accodamento della seconda alla prima, quindi della terza alla stringa cos ottenuta,
e si continua in questo modo fino all'ultima stringa. La concatenazione si pu
indicare con +,&,//. Ad esempio, date le due stringhe:

s1 = abc s2 = efg

la concatenazione s1 + s2 definisce la stringa abcefg.


Sulle sequenze sono anche definite le operazioni di estrazione del primo
elemento first(s1) = a, e dell'ultimo elemento last(s1) = c.
I tipi strutturati che sono presenti nella maggioranza dei linguaggi di
programmazione (nel senso che il linguaggio offre i costruttori per la loro
definizione) sono: array monodimensionale, bidimensionale e pluridimensionale,
record, stringa di caratteri, file sequenziale.
Per i tipi: pila o stack, coda, tabella, anche se assenti dai linguaggi di
programmazione, verr comunque data la definizione per il ruolo importante che
essi assumono in una molteplicit di elaborazioni.
166 Capitolo quinto

In generale l'assenza di un costruttore di tipo, non deve condizionare il


programmatore dall'uso di un tipo astratto. Difatti solo la definizione precisa
(struttura e operazioni) del tipo pu portare ad una sua reale definizione o
simulazione coi meccanismi presenti nel linguaggio usato.
5.4.1. Gli array
Con l'array monodimensionale, anche detto vettore, si assegna un nome
collettivo ad un insieme ordinato e finito di oggetti dello stesso tipo. L'insieme
ordinato nel senso che ogni elemento occupa una posizione ben precisa ed finito
perch il numero di elementi che lo compongono per motivi pratici limitato.
L'array pu anche essere definito come il prodotto cartesiano di elementi tutti dello
stesso tipo e con lo stesso attributo.
La funzione di accesso ottenuta specificando la posizione della componente
nella ennupla del prodotto cartesiano. Per, se si fissa una corrispondenza tra un
insieme di valori e l'insieme delle posizioni nella ennupla, allora si pu utilizzare
tale insieme di valori come funzione di accesso. Questo insieme detto insieme
indice ed ha la propriet che ad ogni suo valore associato un solo elemento
dell'array secondo il seguente schema.

Figura 2: Mapping tra indici ed elementi di un array


I tipi subrange dei tipi intero, carattere e di quello definito per enumerazione
sono usabili come tipi indice. Difatti la corrispondenza tra il valore del tipo e la
posizione nell'array definita implicitamente dalla posizione del valore stesso
nell'intervallo. Il tipo indice definisce cos l'intervallo di valori che pu essere
specificato nella selezione di un componente dell'array.
Per quanto riguarda le operazioni sull'array, esse sono tutte quelle definite sul
tipo delle informazioni componenti. Allora un tipo strutturato si definisce come
array quando sono specificati:
- l'attributo dell'array;
- il tipo dei componenti;
I dati 167

- il tipo dell'informazione indice;


- il numero dei componenti chiamato anche cardinalit dellarray e
implicitamente definito dalla cardinalit del tipo indice.
Si noti che solitamente si usano intervalli del tipo intero come tipo per
l'indice. In questo caso il numero di componenti dell'array dato da estremo
superiore meno estremo inferiore pi uno perch entrambi gli estremi devono
essere considerati. In alcuni linguaggi l'estremo inferiore si pu omettere quando
coincide con il valore uno, e si deve quindi indicare soltanto l'estremo superiore. In
questi casi le due definizioni di array di 10 numeri reali:

type vettore=array [1..10] of integer


e
type vettore=array [10] of integer

sono equivalenti.
La selezione di un componente si effettua facendo seguire al nome dell'array il
valore dell'indice racchiuso tra parentesi quadre: con vettore[1] si accede al primo
elemento dellarray se lindice parte da uno. anche possibile selezionare un
elemento dell'array mediante espressioni o variabili il cui valore sia un valore
appartenente al tipo indice. In questo caso la posizione viene calcolata
dinamicamente durante l'elaborazione.
Ad esempio vettore[I-1] comporta la determinazione del valore della variabile
I e il suo decremento di una unit per individuare la posizione nell'array. Allora se I
ha il valore 4 vettore[I-1] coincide con vettore[3], mentre vettore[I] coincide con
vettore[4]. Per questo motivo la posizione in genere un parametro d'uso nel senso
che dipende dalla elaborazione in corso: in altre parole la selezione dell'elemento
dell'array fatta dopo aver valutato il valore della variabile o dell'espressione.
Come gi detto, le operazioni sull'array sono tutte quelle definite sul tipo delle
informazioni componenti. Qualche volta possibile l'assegnazione del valore di
tutti i componenti di un vettore B a quelli di un altro vettore A dello stesso tipo. In
questi casi A:=B corrisponde a:

A[1]:=B[1]
A[2]:=B[2]
: ::
: ::
A[n]:=B[n]

o addirittura sono consentite operazioni di somma, prodotto, divisione, per


cui, ad esempio, A:=B+C corrisponde a:

A[1]:=B[1]+C[1]
A[2]:=B[2]+C[2]
: :: ::
A[n]:=B[n]+C[n]
ma sono casi molto rari.
In una elaborazione non detto che tutti gli elementi di un array vengono
necessariamente utilizzati. Difatti avviene spesso che solo una parte del numero
totale di elementi utile alla elaborazione in corso. Per tali motivi si soliti
168 Capitolo quinto

accompagnare la struttura array con una ulteriore informazione di tipo intero che
conta quante sono le posizioni occupate durante lelaborazione. A tale
informazione viene dato il nome di grado di riempimento dellarray o
semplicemente riempimento.
Il riempimento rappresenta il numero di componenti effettivamente usato
nella elaborazione in corso e non va confuso con il numero di componenti
dell'array (cardinalit dell'array). Solitamente il riempimento minore o al
massimo uguale alla cardinalit. Allinizio del programma il riempimento deve
assumere il valore zero per indicare che nellarray non sono stati inseriti valori.
Diverse possono essere le strategie per disporre valori diversi nelle diverse
posizioni dellarray. Il modo pi semplice di inserirli in posizioni successive a
partire dalla prima cosicch ad ogni inserimento il riempimento non solo indica
quanti elementi sono stati allocati ma anche quale lultima posizione occupata.
Nella tabella 9 che segue si presentano diversi algoritmi che inseriscono nellarray
vet gli elementi prodotti da una funzione generica produci_valore; in tutti i casi il
riempimento indicato dalla variabile n mentre la cardinalit dalla costante cmax.
Negli esempi si distinguono i casi in cui la prima posizione dellarray zero da
quella in cui uno. Nei primi due esempi il numero di elementi prodotti
predeterminato.

Caricamento Caricamento Caricamento Caricamento


vettore con vettore con vettore con vettore con
posizione iniziale posizione iniziale posizione iniziale posizione iniziale
uguale a zero e uguale ad uno e uguale a zero e uguale ad uno e
numero elementi numero elementi numero elementi numero elementi
predeterminato predeterminato non predeterminato non
predeterminato
n := fissa_riemp; n := fissa_riemp; n:=0; n:=0;
for i:=0 to n-1 for i:=1 to n while (termina while (termina
do vet[i]:=produci_valore; do vet[i]:=produci_valore; AND n<cmax) AND n<cmax)
do begin do begin
n:=n+1; n:=n+1;
vet[n-1]:=produci_valore; vet[n]:=produci_valore;
termina:= termina:=
verifica_terminazione; verifica_terminazione;
end end
Tabella 9 - Caricamento di vettori

In tutti i casi n indica il riempimento del vettore: nel caso di uso della
posizione zero n indica la posizione successiva a quella occupata per ultima; negli
altri casi indica proprio lultima posizione occupata. Si noti che la scelta della
cardinalit di un array (cmax negli esempi) di estrema importanza in quanto deve
essere fatta pensando che lo stesso programma pu essere eseguito in condizioni
diverse: cmax deve comprenderle tutte in quanto un suo sottodimensionamento
porta il programma in una condizione di errore gestita in modi diversi dal
compilatore del linguaggio.
Negli esempi luscita dal ciclo per n<cmax non pi verificata, comporta che
alcuni dati prodotti dallambiente non vengano presi in considerazione. In
definitiva non si deve sovradimensionare l'array introducendo componenti mai
utilizzati occupando inutilmente la memoria centrale, e non si deve neppure
introdurre un array troppo piccolo per le diverse richieste dell'elaborazione.
I dati 169

Un array si dice bidimensionale se i suoi componenti sono array


monodimensionali. Una possibile definizione di un tipo array bidimensionale di
interi la seguente:

type matrice=array [1..10,1..10] of integer

In questo caso la funzione di accesso composta da due indici:


- il primo che individua l'array componente;
- il secondo che individua il componente dell'array selezionato con il primo
indice.
Ad esempio matrice[I,J] il componente di posto J dell'array I-esimo. Gli array
bidimensionali vengono usati nei problemi di analisi per la rappresentazioni delle
matrici. Gli esempi che seguono riportano alcune modalit di uso degli array
bidimensionali.

Caricamento matrice Caricamento matrice non Copia di una


quadrata per righe quadrata per colonne matrice in unaltra
invertendo righe e
colonne (entrambe
quadrate di ordine
n)
n:=fissa_ordine; n:=fissa_ordine; for I:=1 to n
for I:=1 to n m:=fissa_ordine; do begin
do begin for J:=1 to m for J:=1 to n
for J:=1 to n do begin do begin
do begin for I:=1 to n A[J,I]:= B[I,J];
matrice[I,J]:=determina_valore; do begin end
end matrice[I,J]:=determina_valore; End
end end
End
Tabella 10 - Caricamento di matrici

Gli array pluridimensionali sono array di array di array con funzione di


accesso A[I,J,K,...]. Solitamente il numero di dimensioni ammesse per un array
finito e definito per motivi pratici.
5.4.2. Il tipo stringa di caratteri
Il tipo stringa di caratteri si definisce come sequenza di caratteri. Per motivi
pratici la lunghezza della sequenza finita. possibile avere stringhe senza
caratteri, ossia con lunghezza nulla. Tali stringhe sono dette stringhe nulle o vuote.
Tra i valori del tipo stringa di caratteri si definisce un ordinamento se il tipo
carattere ordinato e il confronto tra due stringhe il risultato del confronto dei
caratteri in egual posizione. Si ha allora che BIANCO' segue 'BIANCA' ( maggiore
di), e 'caso' precede 'casolare' ( minore di). In particolare, per le stringhe che
presentano la medesima lunghezza l'ordinamento quello lessicografico: due
parole si confrontano carattere per carattere a partire da sinistra (carattere in prima
posizione) e l'ordinamento viene deciso dalla prima occorrenza di caratteri diversi
e si dice che la parola che ha il carattere pi piccolo precede l'altra. Per le stringhe
con lunghezza diversa si estende l'ordinamento lessicografico accodando alla
stringa pi corta il numero di spazi necessari a renderla lunga quanto l'altra. Si noti
170 Capitolo quinto

per che l'ordinamento tra le stringhe diverso da quello del dizionario per il quale
non si fa differenza tra lettere maiuscole e minuscole e sono assenti i caratteri
speciali e le cifre.
Qualche volta definita una funzione di accesso che consente di estrarre da
una stringa assegnata la sottosequenza che inizia in una posizione specificata ed
costituita da un numero fissato di caratteri, oppure termina in una data posizione.
Un'operazione interna al tipo stringa di caratteri (come gi visto applicabile a
tutte le sequenze) la concatenazione, che applicata a due sequenze, la prima di
lunghezza M, la seconda di lunghezza N, genera una sequenza di lunghezza M+N
avente come primi M simboli quelli della prima sequenza e come successivi N
simboli quelli della seconda sequenza.
Di solito si indica con &,//,+, ed ad esempio, se s1='CA' e s2='SA' allora si
ha che s1 + s2 'CASA'. Si ricordi che per definire una stringa sono necessari:
- l'attributo della stringa;
- il numero massimo di caratteri che la stringa pu contenere (solitamente
0 < K < MAX, con MAX che dipende dal linguaggio di programmazione).
Ad esempio:
type PAROLE = string[10]
type TELEFONO = string[12]

Per le stringhe di caratteri valgono considerazioni analoghe a quelle fatte per


il riempimento di un array. La lunghezza della stringa dipende dalla elaborazione
in corso e risulta sempre minore od uguale alla massima dimensione. I linguaggi di
programmazione per gestiscono in modi molto diversi la lunghezza della stringa.
In particolare alcuni consentono di utilizzare solo una parte di quella massima
dichiarata altri no. Allora si dice che una stringa ha una lunghezza variabile se
possibile usare solo una parte dei K caratteri previsti nella definizione. Sulle
stringhe gestite in questo modo definita la funzione LEN(x) che fornisce il numero
di caratteri presenti nella stringa (valore compreso in [0,K]). Si dice invece che una
stringa ha una lunghezza non variabile se vengono usati sempre tutti i K caratteri
previsti nella definizione.
Per comprendere la differenza fissiamo che s1 sia string[10] e s2 sia string[5] e
osserviamo il funzionamento della concatenazione s1 + s2 dopo aver effettuato le
seguenti assegnazioni:
s1:='abc' e s2:='efg'

1. caso variabile: i len(s2) caratteri di s2 vengono accodati ai len(s1) caratteri


di s1 dando la stringa 'abcefg'
2. caso non variabile: i 5 caratteri di s2 vengono accodati ai 10 caratteri di s1
fornendo la stringa 'abc efg '
Si noti che assegnare ad una stringa un'altra stringa di lunghezza minore della
sua dimensione non crea alcun problema nel caso variabile mentre in quello a
lunghezza fissa comporta che i restanti caratteri vengono solitamente riempiti con
il carattere spazio. Ad esempio l'assegnazione s1:='abc' assegna ad s1 definita come
string[10] la stringa 'abc' nel caso a lunghezza variabile e 'abc ' nell'altro caso.
Invece l'assegnazione ad una stringa di un'altra di dimensione maggiore non
presenta alcuna differenza nei due casi e si traduce in un troncamento dei caratteri
I dati 171

eccedenti. Ad esempio l'assegnazione ad s1 di 'abcdefghilmnopqrstuvz' corrisponde


all'assegnazione della sola stringa 'abcdefghil' (primi 10 caratteri).
Per il caso a lunghezza fissa bisogna per fare attenzione agli effetti combinati
della concatenazione e dell'assegnazione: difatti l'assegnazione s1:= s1 + s2 risulta
inutile in quanto per effetto della concatenazione vengono presi tutti i caratteri di s1
e composti con quelli di s2, ma per effetto della assegnazione la stringa ottenuta
verr troncata proprio dei caratteri di s2.
5.4.3. Il record
Il tipo strutturato record il prodotto cartesiano di informazioni dette campi: i
tipi dei campi possono essere omogenei o disomogenei. Per definire un record sono
necessari:
- l'attributo del record;
- e l'elenco degli attributi dei campi con relativi tipi.
Ad esempio:

type NOME = record of


NOME_PRIMO_CAMPO: TIPO_A;
NOME_SECONDO_CAMPO : TIPO_B;
:
NOME_I-esimo_CAMPO: TIPO_A;
:
NOME_N-esimo_CAMPO: TIPO_X
end;

type COORDINATE = record of


ASCISSA: real;
ORDINATA: real;
end;

type DATA = record of


GIORNO: integer;
MESE: integer;
ANNO: integer;
end;

La funzione di accesso del record consente la selezione di un campo. Si


realizza specificando il nome del record e quello del campo separati da un punto.

Ad esempio se A una variabile del tipo:

type ANAGRAFICA =
record of
NOME: string[40];
INDIRIZZO: string[30];
ETA: 1..100;
ESAMI_SOSTENUTI: integer
end
172 Capitolo quinto

allora per selezionare il campo NOME si deve scrivere A.NOME, mentre per ETA,
A.ETA e cos via.
Le operazioni sul record sono quelle definite sul tipo delle informazioni
componenti. Ad esempio:
A.NOME *4

non possibile, mentre:

A.ESAMI_SOSTENUTI +30

ovviamente possibile.
In alcuni linguaggi possibile l'assegnazione ad un record del valore di un
altro record dello stesso tipo. In questo caso il valore di tutti i campi del record a
secondo membro vengono assegnati ai campi con medesimo nome del record a
primo membro. Ad esempio, se dat1 e daT2 sono due record del tipo DATA, allora
l'assegnazione dat1:=dat2 corrisponde all'insieme delle assegnazioni:

dat1.giorno:=dat2.giorno
dat1.mese:=dat2.mese
dat1.anno:=dat2.anno

5.5. I puntatori
Una variabile detta puntatore se il suo valore individua la posizione di
un'altra informazione. Per posizione di una informazione si intende la posizione da
essa assunta o in memoria o all'interno di una struttura dati. In tal senso sono
puntatori i riferimenti ad un elemento di un array, di un carattere all'interno di una
stringa, e cos via.
Nei linguaggi attuali, nei quali possibile la creazione e la distruzione di
variabili dinamicamente, durante l'esecuzione del programma sotto il diretto
controllo del programmatore, i puntatori sono lunico strumento per poter operare
su tali variabili. Con comandi specifici (ad esempio new) si istanziano variabili
quando servono nel corso della elaborazione; con altri comandi (ad esempio
dispose) si recupera lo spazio di memoria occorso per lallocazione della variabile.
La istanziazione della variabile restituisce lindirizzo di memoria attraverso cui si
accede ad essa per tutte le operazioni consentite compatibili con il tipo di
appartenenza.
Per poter far uso di un tale meccanismo di allocazione dinamico, le variabili
devono essere opportunamente dichiarate per consentire non solo la gestione
dellindirizzo di memoria attraverso cui operare sulla variabile, ma anche la sua
effettiva occupazione in memoria. La definizione di una variabile puntatore si
effettua quindi indicando a quale tipo di variabile essa deve puntare come
lesempio seguente schematizza.

type tipo_puntato = record of


campo1: integer;
I dati 173

campo2: real;
end;
type puntatore =^tipo_puntato;

Con essa si definisce il tipo puntatore come riferimento al record di tipo


tipo_puntato. Allora la dichiarazione:

var punt : puntatore;

crea una variabile il cui contenuto sar l'indirizzo dellarea di memoria gestita
secondo le indicazioni di tipo della definizione e allocata dinamicamente. In altre
parole, l'inserimento all'interno del programma della seguente chiamata:

new(punt);

provoca l'allocazione in memoria di una variabile di tipo tipo_puntato (un


record quindi) e l'assegnazione alla variabile puntatore dell'indirizzo di allocazione.
Senza tale chiamata la variabile di tipo record, riportata nell'esempio, non pu
essere usata e solo dopo di essa si pu fare riferimento alla variabile creata tramite
la variabile puntatore. Per tali motivi si ha che:
- punt denota il valore del puntatore (ossia l'indirizzo di memoria dove il
record stato allocato per effetto della procedura new);
- punt^ denota invece la variabile puntata (quindi il record).

Cos la seguente assegnazione:

punt^.campo1 := 1000;

assegna il valore 1000 al primo campo del record.

Se infine si dichiara unaltra variabile dello stesso tipo puntatore:

var punt1: puntatore;

ed ad essa si assegna il valore del precedente puntatore:

punt1 := punt;

si ha che l'indirizzo di memoria associato a punt viene assegnato anche a punt1 in


modo che entrambi fanno riferimento alla stessa zona di memoria.
Con un meccanismo opposto al new, la procedura predefinita dispose
restituisce lo spazio di memoria associato al puntatore specificato nella chiamata
della procedura stessa. Cos:

dispose(punt)

rende disponibile per altre allocazioni l'area di memoria a cui fa riferimento punt.
174 Capitolo quinto

I linguaggi che dispongono di puntatori di questo tipo, ossia contenenti


direttamente indirizzi di memoria, devono fornire anche un valore che indichi che
il puntatore non punta a nulla. Solitamente tale costante predefinita e si indica
con NIL.

5.6. I file
Il file si definisce come una sequenza di elementi tutti dello stesso tipo. Si
possono cos avere file di interi, di caratteri, di record, di array, di reali, etc.
L'importanza di tale struttura che di essa non se ne deve indicare la dimensione
all'atto della definizione in quanto i suoi valori vengono allocati in una delle
memorie di massa disponibili nel sistema informatico. Tra laltro, mentre la
dimensione della memoria centrale da considerare definita e limitata, e quindi
sono limitati gli oggetti in essa inseribili, le memorie di massa hanno una capacit
cos grande da non rappresentare, solitamente, una limitazione al numero di
elementi gestibili. Comunque, pu accadere che durante l'aggiunta di informazioni
ad un file si generi un errore per la saturazione della memoria di massa non
essendo essa di fatto illimitata.
Poich le informazioni contenute nel file devono essere gestite nel rispetto del
modello di Von Neumann, al file viene fatto corrispondere un buffer in memoria
centrale capace di garantire il transito dei valori da e verso la memoria di massa.
Per operazioni di scrittura il buffer viene caricato del valore che successivamente
viene registrato sulla memoria di massa, per quelle di lettura si procede in modo
opposto, il valore viene letto dalla memoria di massa e quindi inserito nel buffer
dove pu essere poi elaborato.
Il file quindi composto da una sequenza di elementi tutti dello stesso tipo T
residenti sulla memoria di massa e da una ulteriore informazione di tipo T che
svolge le funzioni di buffer in memoria centrale. I file possono essere ad accesso
diretto o sequenziale. Nel primo caso si pu accedere direttamente ad un
particolare dato nel file nota la posizione; nel secondo caso laccesso al file
avviene in modo rigorosamente sequenziale (cio prelevando un valore dopo
l'altro), indipendentemente dal particolare supporto impiegato per la sua
memorizzazione.
5.6.1. I file sequenziali
Le operazioni definite su un file sequenziale di nome F sono:
- l'inserimento di un elemento PUT(F);
- l'estrazione di un elemento GET(F);
- l'operazione di riavvolgimento RESET(F) per predisporre il file alle
estrazioni;
- la cancellazione o azzeramento REWRITE(F) per predisporre il file alle
inserzioni
- e il predicato logico EOF(F) che segnala durante le estrazioni se il file
terminato (End Of File).
Il buffer viene inserito automaticamente all'atto della definizione, lo si indica
con F^ se F una variabile di tipo file ed di tipo T se il file composto da
elementi di tipo T. Il buffer l'unica informazione del file in memoria centrale che
I dati 175

consente di leggere o aggiungere componenti ad F permettendo il transito da e


verso la memoria ausiliaria.
In ogni momento il file pu essere visto come la concatenazione di una parte
destra Fd di una parte sinistra Fs (prendendo come riferimento l'organo di lettura e
scrittura):

F:=Fs+Fd

Figura 3: Parte destra e sinistra di un file

In tali condizioni EOF(F) assume il valore true se Fd vuoto, negli altri casi
false. Se EOF(F) falso, ossia la parte destra non vuota, si definisce il buffer
come:
F^:=first(Fd)

Nel caso di un file composto dalla sequenza di elementi (abcd) si ha che


l'esecuzione di un RESET(F) comporta l'inizializzazione del file (riavvolgimento) e il
posizionamento nel buffer del primo elemento secondo lo schema:

Fs :=<file vuoto>
Fd :=abcd (*intero file*)

e, quindi, per definizione:

F^:=first(Fd) ossia F^=a perch EOF(F) falso

L'esecuzione di un GET(F) porta invece nel buffer l'elemento del file


successivo a quello precedentemente prelevato per effetto di un RESET o di un altro
GET secondo lo schema:

se Fs:=ab e Fd:=cd allora la GET produce Fs:=abc e Fd:=d

e, quindi, per definizione


176 Capitolo quinto

F^:=first(Fd) ossia d (in quanto EOF(F) ancora falso).

L'esecuzione di un REWRITE(F) cancella gli elementi del file svuotandolo:

F=<file vuoto>

L'esecuzione di un PUT(F) accoda il contenuto del buffer agli elementi del file
secondo lo schema:

se F:=abcd e F^:=f allora put produce F:=F+F^, ottenendo F:=abcdf

solitamente il buffer F^ indefinito dopo un PUT.


Nei file sequenziali l'ordine in cui le operazioni possono susseguirsi molto
rigido. Dopo un reset sono possibili solo operazioni di get, mentre dopo un rewrite
solo quelle di put.
Si definisce generazione di un file l'insieme di operazioni necessarie alla
scrittura di un file su memoria di massa: esse sono un rewrite e una o pi
operazioni di put. Si definisce, invece, ispezione di un file l'insieme di operazioni
necessarie alla lettura di un file: esse sono un reset e tante operazioni di get fino a
che non si incontra la fine del file (eof diventa vero). Si osservi che:
- la definizione delle operazioni ci impone che l'accesso ad un elemento
possibile solo dopo l'accesso a tutti gli elementi che lo precedono;
- non possibile mischiare operazioni di put e get;
- durante l'ispezione del file le componenti vengono lette secondo la
disposizione sequenziale fissata durante la scrittura.
I linguaggi di programmazione mettono a disposizione procedure di lettura e
scrittura per i file pi semplici da usare. Sono procedure, ad esempio di read e di
write, in cui il buffer risulta nascosto e in cui si fa riferimento direttamente alla
variabile il cui valore deve essere letto o scritto su file. Ovviamente le variabili che
compaiono nelle istruzioni di read e write devono essere dello stesso tipo delle
informazioni che compongono il file.

Procedura Sequenza equivalente


read(F,var); var:=F^
get(F);
read(F,var1,var2,..,varN) read(F,var1);
read(F,var2);

read(F,varN);
write(F,var); F^:=var;
Put(F);
write(F,var1,var2,..,varN) write(F,var1);
write(F,var2);
............
write(F,varN);
Tabella 12 Operazioni di lettura e scrittura su file
I dati 177

Con tali operazioni, l'ispezione di un file con elaborazione dei valori si


presenta nel modo seguente:

(*M: posizionamento all'inizio del file *)


reset(f);
(*M: scorrimento fino alla fine di esso *)
while not eof(f)
do begin
(*M: leggo dal file *)
read(f,info);
elabora(info)
end

La generazione del file si presenta invece nella forma:

(*M: predisposizione del file alla scrittura *)


rewrite(f);
repeat
(*M: produci dati con una prefissata elaborazione*)
produci(info);
if info <> terminazione
then (*M: scrittura su file *)
write(f,info)
until info = terminazione
5.6.2. I file di caratteri o textfile
Un tipo di file molto comuni sono i file di caratteri, detti anche textfile. Fra
tutti i file, i textfile svolgono un ruolo molto importante in quanto, nella
maggioranza dei casi, i dati di ingresso e di uscita dei programmi sono costituiti da
textfile. Un processo di calcolo che interagisce con il mondo esterno pu essere
considerato come un processo di trasformazione di textfile.
Difatti, l'insieme di dati prelevati da una tastiera sono di fatto un textfile (detto
di input) cos come un textfile (detto di output) l'insieme dei risultati presentati
sul video o sul foglio di carta di una stampante. L'uso di alcuni caratteri di
controllo ASCII permette di organizzare i textfile in linee o righe. Ciascuna riga
contiene al suo interno le rappresentazioni (sequenze di caratteri) che servono
all'elaborazione nel caso dell'input o che da essa vengono generate nel caso
dell'output. In particolare, si conviene di usare il CR (Carriage Return) come
separatore di righi e lo spazio (Space) come separatore di informazioni.
Il prelievo da parte del processore di informazioni dal file di input prende il
nome di operazione di lettura mentre l'inserzione di informazioni in quello di
output viene indicata come operazione di scrittura.
Il passaggio dal tipo dell'informazione letta (o scritta) alla sequenza di
caratteri che la rappresentano nel file di input (o di output) prende il nome di
trasformazione di formato ed avviene durante l'elaborazione. Opportune
indicazioni guidano la trasformazione. Tali indicazioni possono essere di tipo
esplicite o implicito. Quelle esplicite sono fornite al processore dal programmatore
178 Capitolo quinto

mentre le implicite intervengono in assenza delle esplicite e sono definite nel


linguaggio di programmazione usato.
In definitiva i file di input e di output sono file particolari. Il file di input un
file che pu essere solo ispezionato e per tale motivo gi generato e resettato
all'esterno del processo di elaborazione ed ammette come unica operazione il get.
Identificheremo l'operazione di lettura del valore di una informazione dal file di
input con:

read(nome_variabile)

Essa corrisponde, detto INPUT il nome del file di input, alla sequenza di
operazioni:
nome_variabile:=INPUT^
get(INPUT)

ossia all'assegnazione alla variabile del contenuto del buffer associato al file
di input e ad un successivo get.
Il file di output un file che pu essere solo generato e per tale motivo
generato all'interno del processo di elaborazione ed ammette come unica
operazione il put. Identificheremo l'operazione di scrittura del valore di una
informazione nel file di output con:
write(nome_variabile)

Essa corrisponde, detto OUTPUT il nome del file di output, alla sequenza di
operazioni:
OUTPUT^:=nome_variabile
put(OUTPUT)

ossia all'assegnazione al buffer associato al file di output del valore della


variabile e ad un successivo put.
Comunque, non solo il mondo esterno (file INPUT per la tastiera e OUTPUT per
il video) organizzato come textfile. Anche altri file possono essere organizzati in
tale modo. Le informazioni che da essi vengono prelevate richiedono
trasformazioni di formato identiche a quelle viste a proposito dell'immissione dei
dati da tastiera, mentre quelle che in essi vengono registrate richiedono le stesse
trasformazioni di formato che governano la presentazione dei risultati al video.
Il vantaggio di organizzare i dati in textfile risiede nel fatto che la
rappresentazione in caratteri ne garantisce la comprensione da parte dell'uomo
anche all'esterno del programma che li genera o li ispeziona. In ogni caso, il lavoro
richiesto per la conversione di valori dalla loro rappresentazione interna (stringhe
di bit) nella forma di sequenze leggibili di caratteri per le operazioni di read e,
viceversa, per quelle di write, richiede del tempo di elaborazione. Se le
informazioni devono essere scritte su un file che lo stesso o un altro programma
deve rileggere, non c' alcuna evidente necessit di convertile dapprima in caratteri
e successivamente riconvertirle nel formato interno di memoria. Inoltre, la
conversione e riconversione oltre a far perdere del tempo pu, nel caso di
informazioni di tipo numerico, generare errori per la differenza di precisione tra la
rappresentazione esterna e quella interna: se ad esempio una variabile numerica
I dati 179

viene scritta sul file con un formato che tronca alcune sue cifre significative,
quando se ne rilegge successivamente il valore, lo si trover diverso da quello
iniziale.
Tutti i file di tipo non testo memorizzano i valori delle informazioni cos come
sono rappresentati in memoria centrale, quindi senza alcuna ulteriore elaborazione.
Essi dipendono fortemente dallambiente di programmazione che li genera e
pertanto richiedono un legame molto stretto tra la loro generazione e ispezione, nel
senso che non solo la successione delle informazioni lette deve corrispondere a
quella in cui le stesse informazioni sono state scritte ma anche che la lettura deve
avvenire nello stesso ambiente in cui sono stati generati. Nei casi in cui i file
vengono generati in un ambiente ed elaborati in un altro, si preferisce utilizzare per
essi il tipo textfile per garantire la portabilit dei dati.
5.6.3. La connessione e sconnessione dei file
La gestione delle memorie di massa affidata al Sistema Operativo. Molti
sono i modi di organizzare le memorie di massa sia dal punto di vista logico che da
quello fisico. In questa sede sufficiente ricordare che, da un punto di vista logico,
le memorie di massa sono organizzate in aggregati di informazioni tra loro
omogenee a cui viene dato il nome di file.
Il file system la componente del sistema operativo a cui affidato il compito di
organizzare e gestire i file. Le sue funzioni specifiche variano da computer a
computer, ma tipicamente includono:
- la creazione e la cancellazione dei file;
- la gestione degli accessi alle informazioni del file;
- la gestione e lorganizzazione dello spazio delle memorie di massa;
- la protezione dei file per impedire l'accesso da parte di persone non
autorizzate a farlo.
Poich su una memoria di massa possono risiedere pi file
contemporaneamente, il file system richiede che a ciascun file, all'atto della sua
creazione, venga assegnato un nome. Tale nome viene poi usato come suo
riferimento per effettuare una qualsiasi altra operazione di gestione come
l'aggiornamento, l'ispezione o la cancellazione. Due file diversi non possono avere
ovviamente lo stesso nome. Il file system gestisce per ogni file, oltre al nome,
anche le seguenti informazioni:
- l'allocazione del file sul supporto magnetico;
- la dimensione del file;
- la data di creazione o dell'ultimo aggiornamento del file;
- le informazioni di controllo sui diritti di accesso quali quelli che abilitano
operazioni di modifica del contenuto del file, la lettura del file o la sua
eventuale eliminazione.
L'informazione fondamentale che contraddistingue il file il nome
attribuitogli al momento della sua creazione. I file system dei vari sistemi operativi
prevedono sintassi molto diverse per la costruzione di tali nomi. Solitamente sono
stringhe di caratteri e cifre di lunghezza fissata attribuite al file in modo analogo a
quello in cui si sceglie un identificatore per una variabile: ossia in modo da
ricordarne il contenuto.
180 Capitolo quinto

Al nome pu essere aggiunta una estensione facoltativa: per esempio nella


stringa filename.est, filename il nome e est l'estensione. L'estensione
comincia con il punto (.), ed formata da 1, 2 o 3 caratteri.
Pi file possono essere raggruppati in cartelle o direttori. Se sulla memoria di
massa sono state organizzate pi cartelle, prevedendo anche che alcune di esse
siano interne ad altre, il nome del file da solo non basta ad identificarlo e si deve
specificare il percorso da seguire per raggiungerlo nella cartella in cui collocato.
Ad esempio \programmi\corso2007\c\ordina.c indica che il file ordina.c si trova
nella cartella c che interna a corso2007 che a sua volta interna a programmi.
Infine un sistema pu disporre di pi memorie di massa identificate da lettere
o da nomi. Se ad esempio il sistema dispone di una unit minidisco e di una unit
disco fisso, l'unit minidisco viene identificata dalla lettera A e il disco fisso dalla
lettera C.
Pertanto per individuare un file, si devono specificare:
- la lettera o il nome identificativi dell'unit,
- il percorso da seguire tra le cartelle per raggiungere quella che contiene il
file
- il nome di file
- e l'estensione, se esiste.

Linsieme dei quattro elementi detto specificazione del file. I due punti (:)
separano lidentificativo dellunit dal resto; le cartelle del percorso sono separate
tra di loro dal carattere \ e lestensione preceduta dal punto.
In un programma, prima di poter operare su un file si deve interagire con il
file system del sistema operativo fornendogli le indicazioni del file capaci di
individuarlo univocamente. La connessione permette all'interno di un programma
di richiedere al sistema operativo il file sul quale operare. Tutti i linguaggi
prevedono a questo scopo procedure predefinite per consentire la connessione tra
la variabile file e il nome da esso assunto nella memoria di massa gestita dal file
system del sistema operativo.
In modo molto semplice la connessione pu avvenire tramite una procedura
che associa alla variabile di tipo f il suo nome sulla memoria di massa. Ad esempio
con una procedura assign del tipo:

assign(F, c:\programmi\corso2007\ordina.c)

si attiva la connessione della variabile F con il file ordina.c residente nella


cartella corso2007, a sua volta interna alla cartella programmi presente sullunit
c:.
L'operazione di connessione deve ovviamente avvenire prima di un reset o di
un rewrite. A partire da essa il programma potr operare in lettura (reset) o scrittura
(rewrite) sul file che sulla memoria di massa ha il nome specificato.
In alcuni linguaggi alla connessione si accompagna anche lindicazione della
modalit di accesso. In essi, con procedure di tipo OPEN, si forniscono le
indicazioni di connessione per ispezione o per generazione come la tabella illustra.
I dati 181

Procedura Modalit Operazioni equivalenti


OPEN(F,nome_file,r) Ispezione Assign(F, nome_file)
Reset(F)
OPEN(F,nome_file,w) Generazione Assign(F, nome_file)
Rewrite(F)
Tabella 13 Procedura OPEN

In alcuni casi si pu anche attivare la scrittura in modalit di accodamento


(append) per aggiungere alla fine di un file esistente.
Cos come si stabilisce la connessione con un particolare file, si pu in ogni
momento sancire la sconnessione tra programma e file. Dopo che stata stabilita la
sconnessione con il file, non pi possibile effettuare operazioni su di esso. Solo
una ulteriore riconnessione ne permette l'uso. Per consentire la sconnessione
esistono procedure tipo close(F). Se non specificata esplicitamente, la sconnessione
avviene in modo automatico all'atto della terminazione del programma.
L'uso della connessione e della sconnessione consente di utilizzare le stesse
procedure di gestione dei file per file diversi all'interno di uno stesso programma.
5.6.4. I file ad accesso diretto (RANDOM)
Esistono numerosi problemi di gestione dei file per i quali l'accesso
sequenziale non adeguato. Si pensi alla gestione di un magazzino dove
l'operazione di registrazione degli articoli avviene in genere una sola volta ma la
modifica del numero di pezzi e del prezzo, per esempio, sono invece operazioni
che avvengono con una elevata frequenza interessando un solo elemento del file. In
questi casi serve una modalit di gestione dei file con la quale posizionarsi
velocemente sullelemento in una data posizione per poterlo modificare senza
dover seguire le procedure rigide di scorrimento tipiche dell'accesso sequenziale.
Oggi, tutti i linguaggi mettono a disposizione del programmatore file ad
accesso diretto (o anche casuale o random) nel senso che possibile operare su un
elemento in una posizione specificata senza dover processare tutti quelli che lo
precedono, come l'accesso sequenziale impone di fare. cos possibile leggere o
scrivere una qualsiasi delle componenti del file, anche se l'aggiunta di componenti
nuove deve avvenire sempre in coda al file stesso. In altre parole i file ad accesso
diretto possono essere visti come una estensione degli array su memoria di massa:
ad ogni elemento del file associata una posizione mediante la quale operare su di
esso con una particolare funzione di accesso. A differenza degli array non si deve
definire la dimensione massima.
Per la connessione dei file ad accesso diretto non richiesta lindicazione
della modalit di accesso. Dopo il posizionamento, mediante lindicazione della
posizione dellelemento su cui operare, si pu operare indifferentemente sia in
lettura con la procedura read che in scrittura con la procedura write.

5.7. Lastrazione sui dati


L'importanza di definire le strutture dati pila, coda e tabella risiede nel fatto
che parecchie applicazioni ne possono richiedere l'uso a prescindere dalla loro
effettiva disponibilit nei linguaggi di programmazione. Infatti, proprio per il gi
182 Capitolo quinto

discusso legame tra algoritmo e struttura dati in esso impiegata, importante poter
trattare ad un primo livello di formulazione della soluzione di un problema con
l'informazione astratta, definita cio tramite la struttura e le modalit di
funzionamento, rinviando, quindi, la definizione dettagliata del tipo con gli
strumenti messi a disposizione dal linguaggio dopo la verifica della correttezza
della soluzione stessa. In questo modo si otterranno soluzioni pi aderenti alla
realt del problema e quindi pi facilmente interpretabili e gestibili nel tempo.
In altri termini l'astrazione sui dati un utile metodo per posporre i dettagli di
implementazione in modo da risolvere i problemi di rappresentazione nella
maniera pi adeguata e quanto pi indipendente dall'algoritmo. L'uso di tale
metodo, peraltro, favorita dalla presenza in alcuni linguaggi pi moderni di
meccanismi appositi di definizione. Invece, nei linguaggi tradizionali, si deve
dapprima scegliere tra quelle disponibili la struttura dati che pi simile per
composizione e modalit d'uso a quella astratta e quindi renderla equivalente, da un
punto di vista funzionale, attraverso i meccanismi del linguaggio, quali le funzioni
o i sottoprogrammi.
5.7.1. Il tipo pila
La pila viene anche detta stack. E' una struttura che possiamo definire come
composta da oggetti tutti dello stesso tipo sui quali possibile operare in modo che
il primo oggetto che dalla struttura si pu estrarre l'ultimo che in essa stato
inserito. Per questa modalit di gestione la pila anche detta struttura LIFO dalle
parole inglesi Last In - First Out (ultimo entrato - primo uscente).
Le operazioni definite sulla pila sono:
- PUSH: per aggiungere un elemento alla pila,
- POP: per togliere alla pila l'elemento che stato aggiunto per ultimo,
- TOP: per osservare l'elemento che stato aggiunto per ultimo.
Inoltre sulla pila definito il predicato:
- EMPTY: che quando assume il valore TRUE indica che la pila vuota,
ossia non ha elementi.
Per motivi pratici, legati all'impossibilit di trattare tipi a cardinalit infinita,
viene introdotto anche il predicato:
- FULL: che con il valore TRUE indica quando la pila si riempita.
Come si pu notare, un tale tipo si presta a modellare l'organizzazione di
oggetti della vita quotidiana. Infatti, come una pila di piatti, di libri, di fogli esso
caratterizzato dal fatto che il primo oggetto prelevabile quello che si trova in testa
a tutti gli altri ed proprio quello che stato posato su di essi per ultimo.
Si noti infine che:
- POP e TOP sono possibili se la pila non vuota;
- PUSH possibile se la pila non piena.
5.7.2. Il tipo coda
La coda una struttura composta da oggetti tutti dello stesso tipo sui quali
possibile operare in modo che il primo oggetto estraibile dalla struttura il primo
che in essa stato inserito. Per questo modalit di gestione la coda anche detta
struttura FIFO dalle parole inglesi First In - First Out (primo entrato - primo
uscente).
I dati 183

Le operazioni definite sulla coda sono:


- PUSH: per aggiungere un elemento alla coda,
- POP: per togliere alla coda l'elemento che stato aggiunto per primo.
Inoltre sulla pila definito il predicato:
- EMPTY: che quando assume il valore TRUE indica che la coda
vuota, ossia non ha elementi.
Per motivi pratici, legati all'impossibilit di trattare tipi a cardinalit infinita,
viene introdotto anche il predicato:
- FULL: che con il valore TRUE indica quando la coda si
riempita.
Per comprendere questa organizzazione di oggetti possiamo pensare ad una
reale coda che si forma davanti ad uno sportello di un ufficio: la persona che
servita per prima proprio quella che arrivata prima nella coda e che si trova
quindi alla sua testa, e le persone che via via arrivano vanno a disporsi sempre
dopo l'ultimo della coda.

Si noti infine che:


- POP possibile se la coda non vuota;
- PUSH possibile se la coda non piena.
5.7.3. Il tipo tabella
La tabella una struttura costituita da un insieme finito di coppie di valori (a,b),
con a e b appartenenti a tipi diversi o anche uguali, tra loro corrispondenti secondo
l'applicazione F:

b:=F(a)

I tipi dei valori possono essere qualsiasi, sia atomici che strutturati. Inoltre il
primo elemento della coppia viene detto chiave della tabella per il fatto che il
mezzo per entrare nella struttura e prelevare il secondo elemento della coppia.
Le operazioni definite sulla tabella sono:
- INSERIMENTO: per aggiungere una coppia alla tabella;
- RICERCA: per estrarre, se esiste, l'elemento b corrispondente al valore della
chiave a specificata;
- CANCELLAZIONE: per eliminare la coppia che ha il primo elemento uguale
al valore della chiave specificata.
Inoltre sulla tabella sono definiti i predicati:
- EMPTY: che quando assume il valore TRUE indica che la tabella vuota,
ossia non ha elementi.
- FAIL: il cui valore TRUE indica il fallimento della ricerca sia per operazioni
di RICERCA che di CANCELLAZIONE, ossia indica che non esiste una
coppia con il valore della chiave specificata.
Per motivi pratici, legati all'impossibilit di trattare tipi a cardinalit infinita,
viene introdotto anche il predicato:
- FULL: che con il valore TRUE indica quando la tabella si riempita.
La tabella una struttura astratta di grande interesse poich permette di
trattare corrispondenze fra insiemi di qualsiasi natura. Ad esempio, si pu pensare
all'elenco telefonico che mette in corrispondenza un nominativo (la chiave) con un
184 Capitolo quinto

numero telefonico, all'indice di un libro (dove il titolo del paragrafo la chiave di


accesso alla pagina), alla rappresentazione di una funzione per punti, e cos via. Si
noti infine che:
- la RICERCA e la CANCELLAZIONE sono possibili se la tabella non vuota;
- l' INSERIMENTO possibile se la tabella non piena.
Capitolo sesto

Il linguaggio C

6.1. Introduzione
Il C un linguaggio di programmazione molto usato in diversi campi
applicativi. Realizzato nel 1972 da Dennis Ritchie, il linguaggio porta un nome che
deriva dal suo predecessore, il linguaggio B, progettato da Ken Thomson per
implementare il sistema operativo Unix. Ritchie e Thomson, successivamente,
riscrissero il sistema Unix interamente in C.
Sebbene il sistema operativo Unix, il compilatore C e quasi tutti i programmi
applicativi di Unix siano scritti in C, il linguaggio non legato ad alcun sistema
operativo o ad alcuna macchina particolare: viene comunque considerato da molti
come un linguaggio di programmazione di sistema per la sua utilit nella scrittura
dei sistemi operativi.
Il C un linguaggio di alto livello che possiede un ristretto insieme di
costrutti di controllo e di parole chiave ed un ricco insieme di operatori; esso
permette, inoltre, di programmare in modo modulare, usando funzioni e macro. Nel
contempo, il C viene anche considerato un linguaggio relativamente di basso
livello, in quanto prevede operazioni tipiche di un linguaggio macchina, come
lindirizzamento in memoria.
Per scelta di progetto il C non contiene istruzioni di ingresso/uscita (non
esistono istruzioni di READ e WRITE dedicate, e neppure istruzioni per laccesso ai
file) n istruzioni particolari per le operazioni matematiche; mancano anche
operazioni per trattare direttamente oggetti strutturati come stringhe di caratteri,
insiemi, liste ed array, ed infine non esistono operatori con i quali manipolare interi
array o intere stringhe. Il C affida limplementazione di tali tipologie di operazioni
a librerie esterne cos da avere un linguaggio compatto le cui funzionalit si
possono ampliare facilmente sia sviluppando ulteriori librerie che modificando
quelle standard.

6.2. Le caratteristiche generali del linguaggio C


Le caratteristiche principali del linguaggio C sono:
- presenza dei fondamentali costrutti di controllo di flusso:
o sequenza di istruzioni organizzate in blocchi;
o decisioni o costrutti selettivi (if, switch);
o cicli o costrutti iterativi (while, for, do).
186 Capitolo sesto

- disponibilit di operazioni su tipi fondamentali (come i caratteri, gli interi,


i reali in virgola mobile) e su tipi derivati, costruiti a partire dai tipi
fondamentali o da altri tipi derivati, (come larray, le funzioni, le strutture
e le unioni).
- gestione dei puntatori con una aritmetica sugli indirizzi.
- invocazione ricorsiva di una funzione, con chiamate nidificate.
- linguaggio case sensitive, nel senso che gli identificatori dipendono dal
modo in cui sono scritti in quanto lettere maiuscole e minuscole sono
considerate diverse.
Il linguaggio C un linguaggio strutturato con precise regole di visibilit
dei dati e del codice. Un programma C composto da un insieme di
sottoprogrammi, che possono essere visti come unit disgiunte, senza per la
possibilit di innesto (ovvero di dichiarare sottoprogammi allinterno di altri
sottoprogrammi in forma annidata). Le unit di programma sono tutte esterne le
une alle altre e sono dotate di propri oggetti locali; possibile tuttavia ricorrere ad
oggetti globali, esterni alle unit ma da esse visibili. Ogni unit costituita da una
sequenza di frasi terminate da punto e virgola che pu essere suddivisa in una
sezione dichiarativa, dove si dichiarano tutti gli oggetti necessari, ed in una sezione
esecutiva dove si trovano le istruzioni vere e proprie atte ad implementare
lalgoritmo associato allunit stessa.
6.2.1. Il vocabolario del linguaggio
Il vocabolario del linguaggio C costituito da sequenze di lunghezza finita di
caratteri trattate come singole entit logiche ( quindi l'insieme delle parole
costruite secondo le regole lessicali). In C ci sono sei classi di simboli:
- separatori ed identificatori,
- simboli speciali
- parole chiave,
- costanti,
- stringhe.
6.2.2. Separatori ed identificatori
Nel linguaggio le diverse entit lessicale sono separate tra loro mediante opportuni
separatori. I caratteri spazio ed ENTER (fine linea) sono considerati separatori
espliciti, mentre gli operatori (aritmetici e di relazione), il punto e virgola e
l'operatore dell'assegnazione (=) vengono implicitamente identificati come
separatori. Cos le frasi che seguono presentano due entit lessicali distinte:

alfa = 10
if contatore

mentre le frasi seguenti ne presentano soltanto una:

ifcontatore
whilecondizione
Il linguaggio C 187

Gli identificatori permettono di indicare i nomi di programmi, costanti,


variabili e funzioni. Un identificatore una stringa di caratteri che deve soddisfare
i seguenti vincoli:
- deve essere composta da lettere, cifre e dal carattere _;
- il primo carattere deve essere una lettera;
- deve essere costituito da un numero limitato di caratteri;
- non deve ovviamente contenere al suo interno lo spazio.

Figura 1 Carta sintattica per identificatori

Sono ad esempio identificatori diversi :


A
a
alpha
ALPHA
Alpha
Sol1
Pi_Greco
eq2Grado
Nella costruzione dellidentificatore si pu usare una delle lettere dellalfabeto
inglese ed una delle dieci cifre arabe. Per quanto riguarda le lettere, si possono
usare sia maiuscole che minuscole, con significato per diverso (alpha, ALPHA,
Alpha ad esempio sono tre identificatori diversi).
6.2.3. I simboli speciali
I simboli speciali sono o semplici caratteri o coppie adiacenti di essi ed indicano le
operazioni presenti nel linguaggio e tutta la punteggiatura richiesta dalla sintassi.
Essi sono:
+ - * /
(operatori aritmetici)

< > == <= >= ~=


(operatori di confronto)

, ; . : ..
(delimitatori e punteggiatura)
188 Capitolo sesto

( ) [ ] { }
(parentesi)

&& || !
(operatori logici)

// /* */
(commento)

Si vuole notare come il C distingua loperatore di assegnazione = da quello


di confronto logico ==.
6.2.4. Parole chiavi
Alcuni identificatori hanno un ben preciso e congelato significato nell'ambito del
linguaggio e pertanto prendono il nome di parole riservate o chiavi. Tali parole
sono riservate per usi particolari e non sono ridefinibili in altro modo dal
programmatore in quanto sono le chiavi che guidano nella costruzione delle frasi
del linguaggio. I seguenti identificatori sono esempi di parole chiavi.

int extern for


char register do
float typedef while
double static switch
struct goto case
union return default
long break
short continue
unsigned if
auto else
6.2.5. I delimitatori
Il linguaggio C usa il carattere punto e virgola (;) come delimitatore esplicito per
terminare tutte le frasi ad eccezione di quelle di commento.
6.2.6. Le frasi di commento
Come tutti gli altri linguaggi di programmazione anche il C consente di introdurre
frasi prive di valore esecutivo o dichiarativo al fine di migliorare la leggibilit e la
chiarezza del programma. Esse servono unicamente ad uno scambio di messaggi
tra le persone e prendono il nome di frasi di commento.
Le frasi di commento iniziano con i caratteri /* e terminano con i caratteri
*/; la presenza di una apertura e di una chiusura del commento permette di
distribuire la frase su pi righe. Ad esempio sono frasi si commento:

/*
questo un possibile
Commento del linguaggio
C
Il linguaggio C 189

*/
e
/************************************************
Nome programma: radice quadrata
Autore: Paolo Rossi
Versione: 1.0
Data: 12/19/2008
*************************************************/

possibile anche introdurre un commento in una unica riga, usando i caratteri


//, in questo caso considerato commento tutto quello che inizia con // e
termina con la fine del rigo (/n nella carta sintattica). I commenti non possono
essere nidificati.

int x_quadro; //definizione della radice quadrata


//segue implementazione algoritmo risolutivo

/* frase */

// frase /n

Figura 2 Carta sintattica per frasi di commento

6.2.7. Le costanti
Le costanti del linguaggio si dividono in costanti numeriche e costanti di tipo
carattere. Per ci che attiene le prime, i numeri trattati dal linguaggio sono di due
tipi: interi e reali. Un numero intero una sequenza di cifre eventualmente
preceduta da un segno. Un numero reale ha in aggiunta una parte decimale ed
eventualmente un fattore di scala. In particolare una costante reale in virgola
mobile si compone di una parte intera, un punto decimale, ed una parte decimale,
ad esempio 12.14. possibile inoltre usare il carattere e oppure E per
lesponente intero con segno come fattore di scala, come in 12.14 E-10. Sono
costanti intere:

10
300
-1000

Sono costanti reali:

3.400
10.20
190 Capitolo sesto

+1.99E+30
-30.008
-0.7E-10
9.02E3

Inoltre per le costanti intere, se la sequenza inizia con 0 (cifra zero), la


costante un numero ottale, altrimenti decimale. Una sequenza di cifre precedute
da 0x o 0X rappresenta invece un intero esadecimale. Una costante intera che
supera il pi grande intero rappresentabile dalla macchina, considerata long. Una
costante intera seguita da lettera l oppure L una costante long. Ad esempio:
012 rappresenta il numero ottale 12, mentre 012L rappresenta il numero ottale 12
di tipo long.

Figura 3 Carta sintattica per costanti numeriche

Una costante di tipo carattere racchiusa tra singoli apici. Il valore di una
costante carattere il valore numerico del carattere nel set di caratteri della
macchina.
Alcuni caratteri non grafici sono rappresentati come di seguito:
newline \n
tab orizzontale \t
backspace \b
carriage return \r
form feed \f
backslash \\
apice singolo \

Figura 4 Costanti di tipo carattere semplice


Il linguaggio C 191

6.2.8. Le stringhe
Le costanti stringa di caratteri sono sequenze di caratteri racchiuse da una
coppia di caratteri doppi apici ("), come in ''salve''. Il valore della stringa dato
dalla sequenza di caratteri esclusi i doppi apici che fungono da parentesi. Una
costante senza caratteri (una coppia di doppi apici) rappresenta la stringa a
lunghezza nulla. Tutte le stringhe, anche quando sono stringhe identiche, sono in
realt diverse. Il compilatore immette un byte nullo,\0, alla fine di ogni stringa
per individuarne la fine. Sono costanti stringhe di caratteri:
''ALFABETO''
''ciao''
''Minuscolo''

Figura 5 Carta sintattica per stringhe

6.3. Il programma e la gestione dei tipi in C


Un programma C un insieme di uno o pi sottoprogrammi o funzioni. Pi nel
dettaglio la struttura di un programma C prevede:
- un insieme di direttive di compilazione (inclusione di librerie esterne,
definizione costanti, etc)
- la dichiarazione di eventuali variabili globali (visibili a tutte le funzioni
del programma), di eventuali alias di tipi definiti dallutente e dei prototipi
delle funzioni
- la specifica di un insieme di funzioni f1(), f2(),,fN() ognuna costituita da
una sezione dichiarativa (variabili locali alla funzione) ed una esecutiva
(sequenza di istruzioni).
Lunica funzione che deve essere sempre presente allinterno di un
programma C chiamata main(), ed la prima funzione a cui viene ceduto il
controllo quando inizia lesecuzione di un programma. Di solito la funzione main()
si occupa del controllo generale delle varie attivit del programma attivando nel
giusto ordine le funzioni di cui il programma si compone. Di seguito riportata la
struttura generale di un programma C.

direttive di compilazione
dichiarazione di variabili globali
dichiarazione alias di tipi
dichiarazione prototipi di funzione

f1()
{
variabili locali
sequenza di istruzioni
}
192 Capitolo sesto

f2()
{
variabili locali
sequenza di istruzioni
}
.
.
.
fN-1()
{
variabili locali
sequenza di istruzioni
}

main()
{
variabili locali
sequenza di istruzioni
}

Un programma C quindi linsieme di una o pi funzioni, tra cui la main();


presenta un preambolo nel quale si elencano le direttive di compilazione, le
dichiarazioni di variabili globali, gli alias di tipi, i prototipi di funzioni.

Figura 7 Carta Sintattica per la rappresentazione di un programma

Una funzione C un blocco di frasi a cui viene assegnato una intestazione che
contiene la dichiarazione del tipo della funzione stessa, il nome e la lista dei
parametri di ingresso/uscita. Il blocco poi iniziato da una parentesi graffa aperta
ed chiuso da una parentesi graffa chiusa come mostrato di seguito.

<tipo della funzione> nome_funzione (parametri)


{
blocco
}

Figura 8 Carta Sintattica per la rappresentazione di un sottoprogramma o funzione

Ad un programma C infine associato un nome che coincide con il nome del


file del codice sorgente (con estensione .c). Il seguente esempio mostra un
Il linguaggio C 193

semplice programma C, di nome salve.c, che effettua la stampa di una frase a


video.

#include <stdio.h>

int main()
{
printf("Salve!\n");
return 0;
}

La funzione printf() scrive a video la sequenza di caratteri, racchiusa tra


virgolette, specificata tra le parentesi tonde. Come gi visto, una sequenza di
caratteri tra virgolette una stringa. Quella dell'esempio si chiude con i caratteri '\'
e 'n', che in coppia hanno, nel linguaggio C, il significato particolare di "vai a
capo". La funzione return serve a ritornare un valore (nel caso in oggetto 0) alla
fine dellesecuzione del programma main.
Infatti, avendo dichiarato che il valore di ritorno della funzione deve essere di
tipo intero, il compilatore si aspetta che la funzione main restituisca un valore
intero alla fine dellesecuzione.
Il programma, pur nella sua semplicit, permette di ricavare un certo numero
di informazioni molto utili sulla struttura generale di un programma in linguaggio
C. Ogni riga, contenente istruzioni o chiamate a funzioni o definizioni di dati, si
chiude con un punto e virgola (carattere che segnala al compilatore il termine
dellistruzione). La prima riga del preambolo una direttiva al compilatore
(#include): con essa si chiede linserimento del file stdio.h allinterno del
programma, a partire dalla riga in cui si trova la direttiva stessa. Nel file stdio.h ci
sono altre direttive e definizioni che servono al compilatore per tradurre
correttamente il programma. In particolare stdio.h contiene non solo la
definizione della funzione printf() utilizzata nel corpo del programma, ma di altre
funzioni di input ed output.
In generale tutti i programmi C includono alcune direttive di inclusione di file
.h, detti include file o header file, il cui contenuto necessario per un corretto
utilizzo delle funzioni di libreria. Il nome dell'include file , in questo caso,
racchiuso tra parentesi angolari (< e >): ci significa che il compilatore deve
ricercarlo solo nelle directory specificate nella configurazione del compilatore. Se
il nome viene invece racchiuso tra virgolette (ad esempio: "mialibreria.h"), il
compilatore lo cerca prima nella directory corrente, e poi in quelle indicate nella
configurazione. Da non dimenticare che le direttive del compilatore non sono mai
chiuse dal punto e virgola.
Tutto quello che si trova tra le due parentesi graffe costituisce invece il corpo
della funzione (function body) e definisce le azioni svolte dalla funzione stessa: pu
comporsi di definizioni di variabili, di istruzioni e di chiamate a funzione.
Come gi ricordato, tutti i programmi C hanno bisogno di una funzione
principale main che fa partire lesecuzione del programma. Un programma
termina quando:
- si raggiunge la fine del main;
- si effettua una chiamata ad una funzione particolare detta exit();
- il programma interrotto in qualche modo
194 Capitolo sesto

- il programma va in una condizione di errore detta anche crash.


6.3.1. Lintestazione di una funzione
L'intestazione di una funzione costituita dai seguenti elementi:
- il tipo della funzione corrispondente al valore da essa restituito alla sua
terminazione; se la funzione non restituisce valori al posto della
dichiarazione di tipo deve comparire void;
- il nome mnemonico ad essa assegnato;
- la lista di parametri di ingresso/uscita contenente la specifica (nome e
tipo) dei parametri formali mediante i quali la funzione capace
scambiare informazioni con gli altri sottoprogrammi.
Come si vedr in seguito, nella specifica dei parametri formali pu essere
usato o meno loperatore *, a seconda che il parametro deve essere scambiato
allatto dellattivazione della funzione per riferimento o per valore.
Sono ad esempio liste di parametri formali i seguenti esempi:
int a, int b
int a, int b, int *c
Nella specifica dei parametri che vanno passati per riferimento bisogna
utilizzare loperatore *. In realt in C lo scambio dei parametri avviene sempre per
copia e nel caso di passaggio per riferimento viene copiato lindirizzo anzich il
valore di una variabile. Di norma i parametri di ingresso di una funzione sono
passati per valore, mentre quelli di uscita o di ingresso/uscita per riferimento.

Figura 9 Carta Sintattica per lintestazione di un sottoprogramma

Sono esempi intestazioni di funzioni:


int main()
void somma (int a, int b, int *c)
int somma (int a, int b)
Si vuole fare notare come grazie allesistenza della dichiarazione void (vuoto)
in C esiste solo la dichiarazione di funzione e le procedure vengono viste come
particolari funzioni che non ritornano alcun valore.
6.3.2. Il blocco di una funzione
Il blocco una entit sintattica che contiene la parte elaborativa di una qualsiasi
unit di programma (programma principale, procedure e funzioni). Esso consiste di
una serie di frasi del linguaggio costituenti la specifica dellalgoritmo racchiuse tra
parentesi graffe.

specfica
{ }
algoritmo
Figura 10 Carta Sintattica per il blocco di un programma
Il linguaggio C 195

6.3.3. I tipi semplici


Il linguaggio C prevede tre dichiarazioni di tipo elementare per le variabili: uno per
le variabili di tipo alfanumerico, uno per le variabili di tipo numerico, ed uno per le
gestione delle variabili void.

Figura 10 Carta sintattica per i tipi elementari del linguaggio

Per ci che concerne le variabili alfanumeriche il tipo utilizzato dal C il tipo


char, cio carattere, corrispondente ad 1 byte. Esso pu assumere 256 valori
diversi, in quanto rappresentato su 8 bit. Si distinguono due tipi di char: il signed
char, con l'ottavo bit usato come indicatore di segno, e lunsigned char, che
utilizza invece tutti gli 8 bit per esprimere il valore, e pu dunque esclusivamente
assumere valori positivi.
Per quanto riguarda le variabili numeriche, il tipo di dato utilizzato per la
rappresentazione degli interi e corrispondente ad una word il tipo int. Anche il
tipo intero pu essere signed o unsigned. Il numero di bit costituenti una word
varia da macchina a macchina. Nellipotesi di una macchina con una word pari a
16 bit, lintero con segno assume valori che vanno da -32768 a 32767, mentre
quelli dell'intero senza segno variano da 0 a 65535.
Per evitare la differenza di rappresentazione tra macchina e macchina, lo
standard definisce anche un tipo particolare detto short int che occupa 16 bit:
spesso short int e int sono equivalenti. Per esprimere valori interi che variano in un
range maggiore, si pu usare il long int, che occupa 32 bit. Anche il long int pu
essere signed o unsigned. I tipi usati per rappresentare tipi interi sono anche detti
integral types.
Per quanto riguarda i tipi reali, il C permette di gestire numeri in virgola
mobile in floating point, a precisione singola e doppia mediante luso
rispettivamente delle parole chiave float e double. Il float occupa 32 bit secondo lo
standard IEEE 754, il double occupa 64 bit ed il long double 80 bit.
A causa della loro funzione le parole chiave signed, unsigned e long sono
anche indicate col nome di modificatori di tipo. La tabella seguente riassume le
caratteristiche dei tipi di dato sin qui descritti.
196 Capitolo sesto

TIPO BIT VALORI AMMESSI


character 8 da -128 a 127
unsigned character 8 da 0 a 255
short integer 16 da -32768 a 32767
unsigned short integer 16 da 0 a 65535
integer 16 da -32768 a 32767
unsigned integer 16 da 0 a 65535
long integer 32 da -2147483648 a 2147483647
unsigned long integer 32 da 0 a 4294967295
floating point 32 da 3.4*10-38 a 3.4*1038
double precision 64 da 1.7*10-308 a 1.7*10308
long double precision 80 da 3.4*10-4932 a 1.1*104932
Tabella 1: Valori ammessi per tipi numerici

Infine per la gestione di variabili booleane o logiche, pur non essendo previsto
nelle prime versioni del linguaggio, stato introdotto il tipo bool. Le variabili di
tipo bool possono assumere i soli due valori logici vero e falso (true e false).
6.3.4. Dichiarazione di variabili
In C necessario dichiarare una variabile prima di poterla utilizzare. La
dichiarazione delle variabili avviene dapprima specificando leventuale
modificatore di tipo, poi il tipo, e, quindi lidentificatore.

Figura 12 Carta sintattica per la dichiarazione di una variabile

Inoltre, in C, le variabili possono essere classificate, oltre che secondo il tipo


di dato, in base alla loro accessibilit e alla loro durata. In particolare, a seconda
del contesto in cui sono dichiarate, le variabili di un programma C assumono per
default determinate caratteristiche di accessibilit e durata; in molti casi, per,
queste possono essere modificate mediante l'utilizzo di apposite parole chiave, in
gergo note come classi di memorizzazione, applicabili alla dichiarazione delle
variabili stesse.
In termini generali, possiamo dire che la durata di una variabile si estende dal
momento in cui le viene effettivamente assegnata un'area di memoria fino a quello
in cui quellarea riutilizzata per altri scopi. Dal punto di vista dell'accessibilit ha
invece rilevanza se sia o no possibile leggere o modificare, da parti del programma
diverse da quella in cui la variabile stata dichiarata, il contenuto dell'area di
memoria riservata alla variabile stessa.
Il linguaggio C 197

Figura 13 Carta sintattica per la dichiarazione di una variabile

Le classi si memorizzazione delle variabili sono riportate di seguito.


- Automatic Qualsiasi variabile dichiarata all'interno di un blocco di
codice appartiene per default a tale classe. Non dunque necessario,
anche se possibile farlo, utilizzare la parola chiave auto; la durata e la
visibilit della variabile sono entrambe limitate al blocco di codice in cui
essa dichiarata.
- Register Dichiarando una variabile con la parola chiave register si forza
il compilatore ad allocarla direttamente in un registro della CPU, con
notevole incremento di efficienza nell'elaborazione del valore in essa
contenuto.
- Static Come nel caso delle variabili automatic, le variabili static sono
locali al blocco di codice in cui sono dichiarate ma hanno durata estesa a
tutto il tempo di esecuzione del programma. Esse, pertanto, esistono gi
prima che il blocco in cui sono dichiarate sia eseguito e continuano ad
esistere anche dopo il termine dell'esecuzione del medesimo. Ne segue
che i valori in esse contenuti sono persistenti; quindi se il blocco di codice
viene nuovamente eseguito esse si presentano con il valore posseduto al
termine dell'esecuzione precedente.
- External Le variabili external sono variabili dichiarate al di fuori delle
funzioni. Esse hanno durata estesa a tutto il tempo di esecuzione del
programma, ed in ci appaiono analoghe alle variabili static, ma
differiscono da queste ultime in quanto la loro accessibilit globale a
tutto il codice del programma. In altre parole, possibile leggere o
modificare il contenuto di una variabile external in qualsiasi funzione.
Infine il linguaggio C possiede il modificatore daccesso const utilizzato in
fase di assegnazione di una variabile per indicare che il valore della variabile stessa
non pu essere modificato durante lesecuzione del programma. Ad esempio:
const float versione=3.1;
ha leffetto di mantenere costante a 3.1 il valore della variabile reale versione.
La visibilit delle variabili, e pi in generale di un identificatore
opportunamente dichiarato, governata da regole precise che possono riassumersi
dicendo che quanto dichiarato allinterno di un blocco non usabile al suo esterno,
mentre vale che se un oggetto dichiarato allesterno di un blocco esso visibile
allinterno di tutti i blocchi che seguono lessicograficamente la dichiarazione.
Nella struttura del programma C, allora gli oggetti dichiarati nel preambolo sono
visibili a tutte le unit di programma e pertanto sono globali; gli oggetti dichiarati
nella sezione dichiarativa di una funzione sono locali ad essa; gli oggetti dichiarati
198 Capitolo sesto

allinterno di un blocco sono ad essi locali e visibili alle frasi che seguono la
dichiarazione stessa.
6.3.5. Alias di tipi
Il C non permette di definire un nuovo tipo allinterno di un programma: tuttavia
permette di introdurre un nome (pseudonimo o alias) che corrisponde ad uno dei
tipi definiti.
typedef nomeTipo nomeNuovoTipo;
Il meccanismo degli alias pu essere molto utile soprattutto per aumentare la
leggibilit di un programma e per evitare espressioni complesse.
6.3.6. Il tipo enumerativo
Tra i tipi fondamentali del linguaggio C va infine annoverato il cosiddetto tipo
enumerativo. Lenumerazione un insieme di costanti intere rappresentate da
identificatori. Le costanti sono dette anche costanti di enumerazione e sono delle
costanti simboliche i cui valori sono impostati automaticamente: iniziano da 0 e
sono incrementati di solito di 1. Un esempio di enumerazione il seguente:

enum GIORNI {LUN, MAR, MERC, GIOV, VEN, SAB, DOM }

creando un nuovo tipo GIORNI i cui identificatori sono associati con gli interi
compresi tra 0 e 6, ovvero:

enum GIORNI {LUN=0, MAR=1, MERC=2, GIOV=3, VEN=4, SAB=5, DOM=6 }

6.3.7. I tipi derivati


Come noto, in un dato linguaggio, a patire dai tipi fondamentali possibile
costruire nuovi tipi, detti tipi derivati. In C i tipi derivati sono vari: tra essi si
prenderanno in considerazione gli array, le strutture, le unioni ed i campi.
6.3.8. Il tipo array
La struttura array (o vettore) composta da un insieme di elementi tutti dello
stesso tipo e con un unico nome collettivo. Pu avere una o pi dimensioni fino ad
un massimo prefissato. Ad ogni elemento dell'array possibile accedere mediante
un indice (o un numero di indici uguale alle dimensioni stabilite) che ne individua
la posizione all'interno della struttura. Il numero di elementi dellarray definito
allatto della dichiarazione e resta inalterato durante il corso del programma.

Figura 14 Carte sintattiche per la costruzione di array


Il linguaggio C 199

La definizione di un array monodimensionale vet con cinque elementi di tipo


intero il seguente:
int vet[5];

Prima si dichiara il tipo (int), poi il nome dellarray (vet) e successivamente il


numero massimo degli elementi dellarray. Per accedere ad un singolo elemento di
un array, il linguaggio C mette a disposizione una funzione daccesso consistente
nel nome dellarray seguito dalla posizione dellelemento (o coppia di posizioni)
racchiusa tra parentesi quadre. La posizione anche detta indice di accesso. Nel
caso dellarray vet composto da 5 elementi lindice pu assumere i valori
0,1,2,3,4. Ad esempio le istruzioni:

vet[1]=70;
vet[3]=1;

assegnano al secondo elemento dellarray vet il valore 70 e al quarto elemento


dello stesso array il valore 1.

Figura 15 Carta sintattica per laccesso agli elementi di un array

Un esempio di definizione di array con pi dimensioni (matrice) il seguente:

int mat[10][15];
float matReal[15][15];

Laccesso agli elementi di un array a due dimensioni simile a quello di un


array ad una sola dimensione, bisogna per in tal caso specificare due indici:
lindice di riga e quello di colonna. Ad esempio listruzione:

mat[1][3]=3;

assegna il valore 3 allelemento della matrice che si torva in corrispondenza


della prima riga e della terza colonna.
6.3.9. Il tipo struct
Il linguaggio C mette a disposizione il tipo struct per la definizione dei tipi record.
La struttura record composta da un numero prefissato di componenti, anche di
tipo differente. Ogni componente, detto anche campo del record, ha un suo nome e
tipo. La definizione di un record prevede, allora, l'elencazione delle variabili, sia
semplici che a loro volta strutturate, costituenti i campi componenti. Le operazioni
consentite sul record sono quelle che si possono fare sui campi.
200 Capitolo sesto

Figura 16 Carta sintattica per il tipo struct

Nellesempio che segue si definiscono il tipo DATA e la variabile data_mese


di tipo record.

struct DATA
int giorno;
int mede;
int anno;
;
struct DATA data_mese;

In maniera pi sintetica si pu anche utilizzare la seguente definizione.


struct DATA
int giorno;
int mede;
int anno;
data_mese;
Laccesso ai valori dei singoli campi del record avviene indicando nome del
record e nome del campo separati da un punto:
nome_struct.nomecampo
ovvero se si vuole ad esempio accedere al giorno della data:
data_mese.giorno
6.3.10. Il tipo unione
Il concetto di unione deriva direttamente da quello di struttura, ma con una
importante differenza: i campi di una union, a differenza di quelli di una struct,
hanno lo stesso indirizzo di memoria e vanno ad occupare, quindi, tutte le
medesime locazioni di memoria. Questo implica che loccupazione di memoria di
una union coincide con quella del campo dellunione di dimensione maggiore. Ad
esempio:
union ghost
int a;
long b;
char c;
;
union ghost x;
ha una dimensione che coincide con quella della variabile b (di tipo long, in
alcune implementazioni come visto pari a 4 byte). Le unioni dunque sono da
preferire alle struct variabili quando servono variabili che possono assumere
diverse dimensioni a seconda delle circostanze.
6.3.11. I campi
I campi sono una particolare struttura C che consente di far riferimento
simbolicamente ai singoli bit di una variabile. Nell esempio seguente:
Il linguaggio C 201

struct
unsigned a:1;
insigned b:7;
variabileBit;

la variabile variabileBit occupa uno spazio in memoria pari alla somma dei bit
utilizzati da ogni campo della struttura. Cos su una word di 16 bit, il campo a
occupa 1 bit; b occupa 7 bit; i restanti 8 sono non utilizzati.
6.3.12. Stringhe di caratteri
Il C non prevede la presenza di un tipo stringa predefinito. Tuttavia, possibile
utilizzare un array di caratteri con alcune convenzioni. Larray:

char stringa[10];

dichiara un array di 10 caratteri, mentre la dichiarazione:

char sentenza[ ]= Salve mondo;

dichiara un array di caratteri in cui numero di elementi dato dalla quantit di


caratteri presenti nella stringa, pi uno, un carattere speciale che indica la fine della
stringa, detto carattere null (\0):

S a l v e M o n D o \0

Il carattere \0 il primo carattere del codice ASCII (NUL) e corrisponde al


codice 00000000. Le operazioni classiche sulle stringhe, quali la copia, la
concatenazione ed il confronto tra stringhe, sono implementate da opportune
funzioni standard di libreria string.h e verranno mostrate in seguito.
6.3.13. I Puntatori
La comprensione e luso corretto dei puntatori sono fondamentali per la creazione
di programmi C. Con essi si possono modificare gli argomenti delle funzioni
quando vengono chiamate ed inoltre consentono lallocazione dinamica della
memoria. Un puntatore una variabile che contiene un indirizzo di memoria: tale
indirizzo rappresenta la posizione in memoria di unaltra variabile. Quando una
variabile contiene lindirizzo di unaltra, la prima detta puntare alla seconda.
Poich una variabile possa essere usata come puntatore, bisogna che sia prima
dichiarata come tale, attraverso la dichiarazione:

tipo *var;

dove tipo un tipo base del linguaggio e var la variabile puntatore.


202 Capitolo sesto

6.4. Gli operatori del linguaggio


Di seguito viene riportata una carrellata degli operatori maggiormente diffusi nel
linguaggio C.
6.4.1. Operatori Aritmetici
Gli operatori aritmetici sono i classici operatori binari +,- ,*,/ e loperatore di
modulo %. La divisione tra interi tronca la parte frazionaria, mentre lespressione
x%y fornisce il resto della divisione di x per y. Loperatore di modulo pu essere
applicato solo a tipi integrali. Gli operatori + e hanno la stessa priorit, priorit
inferiore a * e / (che invece hanno identica priorit).
6.4.2. Operatori Relazionali
Gli operatori relazionali sono:
> >= < <=

mentre gli operatori di eguaglianza (e disuguaglianza) sono:


== !=

Gli operatori di relazione hanno priorit maggiore rispetto a quelli di eguaglianza


/disuguaglianza. Gli operatori relazionali hanno invece priorit minore rispetto agli
operatori aritmetici.
6.4.3. Operatori Logici
Gli operatori logici sono:
&& (and), || (or) e ! (not)
&& ha priorit maggiore di || ed entrambi hanno priorit inferiore agli operatori
relazionali e di uguaglianza. Si noti che non esistendo un tipo logico predefinito,
loperatore ! converte un operando non zero in un operando zero.
6.4.4. Operatori di incremento e decremento
Il C fornisce due operatori inusuali nei linguaggi ad alto livello, utili per
incrementare e per decrementare le variabili, ovvero loperatore ++ e loperatore --
, con notazione prefissa o postfissa, come in esempio:

x++
++x
x- -
- -x

Sia nella notazione prefissa che postfissa si ha come effetto quello di


incrementare (o decrementare) una variabile. Tuttavia ++x (- -x) incrementa
(decrementa) x prima di usarne il valore, mentre x++ (x- -) incrementa
(decrementa ) x dopo averne usato il valore.
Il linguaggio C 203

6.4.5. Operatori sui puntatori


Esistono due operatori per la manipolazione dei puntatori: & e *. Il primo un
operatore unario che restituisce lindirizzo di memoria delloperando cui
applicato. Ad esempio:

indirizzo_x=&x;

pone nella variabile indirizzo_x lindirizzo di memoria della variabile x. Il secondo


un operatore unario che restituisce il valore della variabile che si trova
allindirizzo indicato nella variabile che lo segue. Ad esempio:

valore_x=*indirizzo_x;

ha leffetto di porre nella variabile valore_x il valore della variabile che si trova in
memoria allindirizzo contenuto nella variabile indirizzo_x, ovvero il valore di x.
Il seguente esempio cerca di chiarire i concetti prima esposti.

int x;
int* indx=&x;
int valx=*indx;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma sui puntatori\n");
printf("Inserisci il numero x: ");
scanf("%d",&x);
printf("\nIl valore di x e': %d\n",x);
printf("\nL'indirizzo di memoria (in esadecimale) a cui si trova x e': %x\n",indx);
printf("\nIl valore di x mediante puntatore e': %d\n",x);

Luso dei puntatori risulta essenziale nello scambio di parametri allatto


dellattivazione di funzioni.
Se ad esempio si vuole realizzare una funzione che effettua la somma e la
differenza tra due numeri, allora i parametri, che alla fine dellelaborazione
dovranno contenere somma e differenza, dovranno essere passati per riferimento
attraverso lutilizzo delloperatore &, mentre nellintestazione e corpo della
funzione, essi dovranno essere gestiti attraverso loperatore * per accedere al valore
puntato. In particolare si ha per la funzione suddetta:

void sommadiff(int a, int b, int *d, int *s) {


*d=a-b;
*s=a+b;
}

mentre lattivazione della funzione avverr come segue:

int a,b,d,s;
sommadiff (a,b,&d,&s);

Lattivazione della funzione ha come effetto quello di passare una copia dei
valori delle variabili a ed b alla funzione (passaggio per valore) e quello di passare
204 Capitolo sesto

per le variabili d e s una copia dellindirizzo di memoria (passaggio per


riferimento) in cui esse si trovano per potere accedere al loro contenuto mediante
loperatore * allinterno della funzione.
Si vuole notare che il tipo array viene sempre passato per riferimento, e quindi
allatto dellattivazione di una funzione non viene utilizzato loperatore & per la
variabile ad esso relativo. Inoltre si ha che la sequenza:

char str[80], *p1;


p1=str;

ha leffetto di copiare nel puntatore p1 lindirizzo del primo elemento dellarray


str. Ci comporta che gli elementi di un array possono essere acceduti (in maniera
sequenziale ) anche mediante lutilizzo dei puntatori. Ad esempio:

str[4] e *(p1+4)

sono equivalenti in quanto ogni elemento del vettore occupa un solo byte di
memoria.
6.4.6. Operatori logici bit oriented
In C sono presenti operatori che consentono la manipolazione dei bit. Essi sono
elencati di seguito.

& AND bit a bit


| OR bit a bit
^ XOR
<< shift a sinistra
>> shift a destra
- complemento ad uno

6.5. La specifica dellalgoritmo in C


La specificazione delle azioni elaborative di un sottoprogramma in C si traduce in
un insieme di enunciati o statement composti. Lo statement composto formato da
una sequenza di statement semplici terminati dal carattere punto e virgola, mentre
gli statement semplici che compongono la specificazione dell'algoritmo denotano
le azioni elaborative vere e proprie che devono essere svolte dall'esecutore
macchina. Tali statement si possono classificare in:
- istruzioni di assegnazione
- richiamo di funzioni
- costrutti selettivi ( if else, switch case)
- costrutti iterativi (while, for)
6.5.1. Istruzioni di assegnazione
L'enunciato o istruzione di assegnazione la pi elementare forma di enunciato.
Esso prescrive che venga dapprima calcolato il valore dell'espressione che si trova
a destra del segno di =, e poi che tale valore venga poi assegnato alla variabile
Il linguaggio C 205

che si trova a sinistra dello stesso simbolo. La variabile e il valore dell'espressione


devono essere di norma dello stesso tipo con alcune eccezioni relativamente alle
variabili numeriche (e.g., se la variabile reale, allora il tipo dell'espressione pu
anche essere intero). Listruzione di assegnazione termina con il delimitatore punto
e virgola.

Figura 16 Carta sintattica per unistruzione di assegnazione

Unespressione, di cui per semplicit non riportata alcuna carta sintattica,


una formula o regola di calcolo che specifica sempre un valore detto risultato
dell'espressione. composta da operandi ed operatori. Gli operandi possono essere
variabili, costanti o valori restituiti da una funzione. Gli operatori coincidono con
quelli del linguaggio e sono classificati in monadici o unari e in diadici o binari a
seconda che prevedano uno o due operandi rispettivamente. Se in una espressione
sono presenti pi operandi, occorre ricordare la sequenza della loro esecuzione nel
caso in cui non sia esplicitata mediante l'introduzione nell'espressione delle
parentesi. Esempi di una istruzione di assegnazione sono:
x = 1;
x = x+1;
x = a+b;
In particolare, nel primo caso si assegna una costante numerica alla variabile
x, nel secondo caso si incrementa di 1 la variabile x, nel terzo caso ad x si assegna
la somma del valore di a con il valore di b. Si noti che in C listruzione del tipo:
Espressione1 = Espressione1 operando Espressione2
pu essere anche espressa con:
Espressione1 operando= Espressione2
Ad esempio, lespressione x = x+1 diventa:
x + = 1;
mentre lespressione y *= y+1 in realt la forma compatta di:
y = y * (y+1);
Una espressione particolare del C la cosiddetta espressione
condizionale, del tipo:
espressione1 ?espressione2 :espressione3
In questo tipo di espressione viene valutata lespressione1: se essa diversa
da zero (vera) allora viene valutata lespressione2, altrimenti viene valutata
lespressione3. Ad esempio lespressione:
z=(x>y)? x:y;
mette in z il valore maggiore tra x ed y.
6.5.2. Richiamo di funzioni
Il richiamo della funzione determina l'esecuzione della funzione indicata. Affinch
il richiamo sia corretto, si deve fornire una lista di parametri di I/O uguale, in
numero e tipo, a quella specificata nella dichiarazione della funzione. La
corrispondenza si stabilisce per ordine, nel senso che al primo parametro attuale
206 Capitolo sesto

associato il primo formale, e cos via. Per i sottoprogrammi di tipo funzioni


bisogna inoltre specificare la variabile in cui verr memorizzato il parametro
duscita.

Figura 18 Carta sintattica per richiamo di funzioni

La forma di richiamo per una funzione la seguente:

variabile=nome_fnzione(par_effettivo1,par_effettivo2,., par_effettivon);

mente per una procedura:

nome_procedura(par_effettivo1,par_effettivo2,., par_effettivon);

I parametri attuali devono essere forniti con rispetto del meccanismo di


sostituzione e del tipo dei parametri indicati nella dichiarazione della funzione o
della procedura. In particolare, se si fissato una sostituzione per valore, si
possono usare come parametri attuali variabili, costanti ed espressioni di un tipo
compatibile, secondo le regole viste a proposito dell'assegnazione di valore, con
quello del parametro formale (si pu ad esempio usare un parametro attuale di tipo
sia intero che reale nel caso di un parametro formale di tipo reale, mentre deve
essere assolutamente di tipo intero nel caso di parametro formale dichiarato intero).
Nel caso di sostituzione per riferimento, si devono impiegare solo variabili e i tipi
dei due parametri devono coincidere.
Esempi di istruzioni di richiamo di funzioni sono le seguenti:

x=somma(a,b);
somma(a,b,&c);
6.5.3. Costrutti selettivi
Gli enunciati selettivi permettono la selezione di azioni elaborative in funzione del
valore di un'espressione booleana e non: ossia si valuta l'espressione e si sceglie
l'istruzione successiva in funzione del valore trovato.
If-else
L'enunciato if-else (se - altrimenti) permette di effettuare la scelta tra due
alternative in funzione del valore di una espressione booleana. Se il valore
dell'espressione TRUE, si sceglie l'alternativa (insiemi di enunciati racchiusi tra
parentesi graffe) introdotta dopo lif, in caso contrario (insiemi di enunciati
Il linguaggio C 207

racchiusi tra parentesi graffe) quella aperta dall'else. In entrambi i casi, dopo
l'esecuzione del blocco selezionato, l'esecuzione stessa continua con l'enunciato
successivo all'if. anche possibile usare una notazione abbreviata nel caso in cui
non esista una delle due alternative, non specificando l'else dell'enunciato. Sono
esempi di if:

if (n>10) {
n=n+1;
}
else {
n=n-1;
}

if (n>10) n=0;

Si noti che, nel caso che lazione elaborativa dellif o dellelse sia composta
da un solo statement semplice, le parentesi graffe non sono obbligatorie. Inoltre si
pu notare come l'uso appropriato dell'incolonnamento (o anche indentazione)
delle strutture innestate l'una dentro l'altra migliori notevolmente la chiarezza del
programma in quanto evidenzia l'ordine di esecuzione dei vari blocchi. allora da
usare tale accorgimento anche quando la disposizione delle istruzioni sui righi non
importante, come nel nostro caso.

if espressione
( )
booleana

statement

statement
{ }
composto

else statement

statement
{ }
composto
Figura 19 Carta sintattica per if-else
Il linguaggio C permette anche di realizzare if annidati, ovvero costrutti di
selezione che appartengono al ramo if o al ramo else di unulteriore istruzione di
selezione. Nel caso di costrutti innestati il C dispone di una regola che assegna
ogni else allif pi vicino che non ne possiede gi uno.
Un esempio il seguente, dove lelse relativo al secondo if:
208 Capitolo sesto

if (x>0) {
if (y>0)
printf (x e y maggiori di 0);
else
printf(solo x maggiore di 0);
}

Switch-case
Listruzione switch permette di scegliere tra pi di due alternative (decisioni
multiple) verificando se il valore di una espressione uguale ad un valore tra quelli
specificati in una lista. In particolare consiste di un'espressione (di tipo numerico o
carattere), detta selettore, e di una lista di enunciati, ciascuno dei quali identificato
da uno o pi valori costanti appartenenti al tipo del selettore. L'enunciato scelto
quello identificato dalla costante che uguale al valore calcolato del selettore.
Solitamente la scansione degli identificatori, per cercare quello con valore uguale
al selettore, avviene in modo sequenziale. Per tale motivo si consiglia di disporre
per ultimi gli identificatori che hanno la minore probabilit di essere scelti. Un
esempio di switch il seguente:
switch(x)

case 0:
case 1:
n++;
break;
case 2:
n- -;
break;
default:
n *=2;

Si noti che listruzione break causa luscita dallo switch. Il caso chiamato
default viene eseguito quando non sono stati soddisfatti gli altri casi dello switch.
Un altro esempio il seguente:
SWITCH(numero_mese) {
CASE {1,3,5,7,8,10,12}
printf(mese di 31 giorni);
break;
CASE {4,6,9,11}
printf(mese di 30 giorni);
break;
CASE 2
printf(mese di 28 o 29 giorni);
break;
DEFAULT:
printf(mese non valido);
}
In questo caso viene verificata lappartenenza del valore della variabile dello
switch ad un tipo enumerativo. Si noti che la non introduzione del break porta a
confrontare il valore del selettore con tutti i casi anche quando uguale ad uno di
essi.
Il linguaggio C 209

Figura 20 Carta sintattica per costrutto SWITCH

6.5.4. Costrutti Iterativi


Gli enunciati iterativi permettono l'esecuzione di un blocco di istruzioni un certo
numero di volte. La terminazione della ripetizione avviene quando sono verificate
certe condizioni che sono calcolate internamente al blocco. Se il numero di
ripetizioni noto a priori, la struttura viene anche detta ciclica o enumerativa.
Il ciclo while
L'enunciato while composto da una espressione logica e da uno o pi enunciati
da ripetere in funzione del valore di tale espressione. L'esecuzione del while
comporta la valutazione dell'espressione e l'esecuzione degli enunciati nel caso in
cui il valore calcolato dell'espressione sia TRUE. Il ciclo ha termine quando
l'espressione assume il valore FALSE per cui, se l'espressione risulta subito falsa, gli
enunciati non vengono mai eseguiti. Un esempio di ciclo while :
while(x > 0)

a = a+x;
x- -;

Si osservi che una volta iniziato, il ciclo pu terminare solo se all'interno di


esso vi sono degli enunciati che modificano il valore di verit dell'espressione: cio
operano sulle variabili che ne fanno parte. comunque possibile uscire da un ciclo
210 Capitolo sesto

while in maniera incondizionata, cio, in maniera indipendente dal valore di verit


dellespressione, attraverso la parola chiave break.
while espressione
( )
booleana

statement
{ }
composto

statement

Figura 21 Carta sintattica per costrutto while

Il ciclo do-while
A differenza dei ciclo while, che verifica la condizione allinizio del ciclo (loop)
stesso, il costrutto do-while la verifica alla fine, con la conseguenza che esso viene
eseguito almeno una volta. In particolare, listruzione viene eseguita, poi viene
valutata lespressione: se vera, listruzione viene ancora eseguita e cos via. Il
ciclo termina quando listruzione diventa falsa.
do

statement
{ }
composto

statement

while espressione
( )
booleana
Figura 22 - Carta sintattica del costrutto do-while

Ad esempio, il seguente programma legge i numeri da tastiera finch si


introduce un numero con valore minore di zero.

do
scanf(%d,&n);
while (n>0)
Il linguaggio C 211

Il ciclo for
Il costrutto for un enunciato iterativo enumerativo o ciclico. Esso deve essere
usato ogni qualvolta il numero di ripetizioni noto a priori.

Figura 23- Carta sintattica per il costrutto for

Lo statement di inzializzazione coincide, nella sua forma pi semplice, con


unistruzione di assegnazione con la quale viene fornito il valore iniziale alla
variabile di controllo del ciclo. Lo statement di condizione rappresenta
unespressione booleana che serve a controllare la terminazione del ciclo, finch
TRUE il ciclo prosegue. Lo statement di aggiornamento definisce il modo in cui la
variabile di controllo cambia il suo valore ad ogni ripetizione del ciclo. Di norma
laggiornamento consiste in un incremento o decremento del valore della variabile
di controllo. Esempi di for sono:

for(int i=0; i<=100; i++)


printf(%d ,i);

for(int i=100; i>=0; i- -)

printf(%d ,i);
somma = somma+i;

Nel primo caso vengono visualizzati a video tutti gli interi compresi tra 0 e
100 (incremento), nel secondo caso gli stessi interi vengono visualizzati ma in
ordine inverso tra 100 e 0 (decremento) e ne viene effettuata la somma. In altri
termini, per il primo for, viene posta inizialmente la variabile di controllo i al
valore 0 e viene richiamata la funzione printf() per la visualizzazione a video di i,
dopodich, al ritorno dalla funzione, viene applicata la condizione di
aggiornamento (in questo caso di incremento) della variabile i e si verifica la
condizione (i<=100). Poich la condizione TRUE si ripete listruzione di
visualizzazione sul nuovo valore della variabile di controllo. Analogamente a
quanto gi descritto, il ciclo si ripete finch i non diventa maggiore di 100. Un
212 Capitolo sesto

discorso simile, ma con aggiornamento a decremento, vale per il secondo for.


Listruzione:

for( ; ;)

implementa di contro un ciclo infinito. Per una piena comprensione dei due
costrutti iterativi for e while, si presentano di seguito due costrutti equivalenti.

for(int i=0; i<=100; i++)


printf(%d ,i);

int i=0;
while (i<=100) {
printf(%d ,i);
i++;
}

6.6. Le librerie di funzioni


Come gi anticipato il C, per scelta di progetto, non supporta direttamente
istruzioni di ingresso/uscita, n istruzioni particolari per le operazioni matematiche;
non esistono nemmeno operazioni per trattare direttamente oggetti strutturati come
stringhe di caratteri, insiemi, liste ed array. Il C affida tali tipologie di operazioni a
librerie esterne di funzioni. per possibile servirsi delle funzioni di una
particolare libreria includendone il corrispettivo file header allinterno del
programma, come gi visto, mediante la direttiva di compilazione:

#include <file header della libreria>

Dopo linclusione possibile servirsi di tutte le funzioni della libreria nota


solo la loro interfaccia, ovvero noti i parametri di ingresso/uscita.
Ad esempio, se il seguente programma vuole utilizzare la funzione getch()
della libreria conio.h per attendere la digitazione di un carattere da tastiera, allora
il suo codice dovr avere la seguente forma:

#include <conio.h>
int main ()
{
getch();
return 0;
}

Un moderno compilatore C mette a disposizione una vasta gamma di librerie


contenenti:
- funzioni di uso generale (stdlib.h),
- gestione dellI/O (stdio.h),
- calcolo matematico (math.h),
- gestione di stringhe (string.h).
Il linguaggio C 213

Di seguito si mostreranno le funzioni principali delle librerie pi comunente


utilizzate, rimandando ad appositi manuali la descrizione approfondita delle varie
librerie del linguaggio.
6.6.1. La gestione dellI/O
Le funzioni comprese nel sistema di input/output del C possono essere raggruppate
in tre grandi categorie: I/O su console (tastiera e monitor), I/O su file bufferizzato e
I/O su file non bufferizzato (Unix-like). Per lutilizzo di tutte le funzioni suddette
richiesta linclusione del file header stdio.h. Tale libreria fornisce alcune
funzioni per le operazioni di lettura e scrittura dei valori delle variabili dai file
standard INPUT e OUTPUT.
In genere il file di INPUT rappresenta la tastiera del sistema di calcolo; quello
di OUTPUT il monitor. Essi possono essere visti come organizzati in un testo
costituito da sequenze di RIGHI separati tra loro dal carattere di fine rigo CR
(Carriage Return). La sequenza di righi terminata dal carattere di fine file.
All'interno di ogni rigo sono distribuite le rappresentazioni delle informazioni
opportunamente separate usando il carattere spazio.
Apertura file
In C la lettura da un file avviene aprendo un canale di comunicazione con la
memoria di massa attraverso la connessione del file. In altre parole un file deve
essere sempre connesso o aperto prima di prelevare dati da esso. Lapertura di un
file avviene attraverso la funzione fopen() della libreria stdio.h:
FILE* fopen (char* filename, char* permission)

Tale funzione apre un file il cui nome (path su disco del file) un insieme di
caratteri puntato dalla variabile filename, mentre la variabile permission
definisce la modalit di apertura del file (e.g., sola lettura, solo scrittura,
lettura/scrittura). La funzione suddetta restituisce poi un identificatore del file noto
anche con nome di puntatore a file, attraverso il quale il file viene referenziato. Se
per vari motivi il file non pu essere aperto viene restituito un puntatore nullo.
Di seguito riportato un esempio dellapertura in sola lettura di un file
pippo.txt contenente un array di numeri interi (i numeri si trovano su righi
differenti di testo separati tra loro dal carattere di fine rigo). La modalit di
apertura a solo lettura utilizza la stringa r, quella a sola scrittura lopzione w,
infine, quella in scrittura/lettura rw.

FILE *fid;
fid=fopen (c:\pippo,txt, r);
if (!fid) printf(FILE NON APERTO);

Di seguito riportato un esempio di apertura di un file in scrittura:

FILE *fid;
fid=fopen (c:\pippo,txt, w);
if (!fid) printf(FILE NON APERTO);
214 Capitolo sesto

Lettura da file
Dopo la connessione del file in modalit lettura, attraverso lidentificatore del file
possibile eseguire le operazioni di lettura che avvengono con lutilizzo della
funzione fscanf() della libreria stdio.h:

int fscanf(FILE*fid, char*format, var)

dove fid lidentificativo (puntatore) del file, format indica il tipo degli
elementi da leggere e var la variabile in cui si inseriscono gli elementi letti. La
funzione ritorna il numero di elementi effettivamente letti, se viene restituito EOF
significa che stata tentata la lettura dopo la fine del file.
La variabile format pu assumere i seguenti valori a seconda del tipo di dato
che si vuole leggere:

%d interi
%f,%g reali
%c caratteri
%s stringhe

Di seguito riportato un esempio di lettura del file dove memorizzato un


array di interi (sul primo rigo del file di testo c il riempimento del vettore):

FILE * fp;
register int i;
int vettore[100];
int riemp, ret;
if (!(fp=fopen("pippo.txt","r"))) {
printf("\n Il file non puo' essere caricato\n");
}
else {
ret=fscanf(fp,"%d",&riemp);
if (ret!=EOF) {
printf("\nNumero di elementi nel vettore: %d\n",riemp);
if (riemp>0) {
for (i=0; i<riemp; i++) fscanf(fp,"%d",vettore[i]);
}
}
else printf("\nfile vuoto!!!\n");
}

La lettura di informazioni da standard input (tastiere) avviene invece, come


gi descritto, attraverso la funzione scanf() (senza bisogno di aprire alcun file)
della libreria stdio.h:

int scanf(char*format, var)


Il linguaggio C 215

dove var rappresenta la variabile in cui verr memorizzato il valore immesso da


tastiera, mentre format ha lo stesso significato per la funzione fscanf. Di seguito e
riportato lesempio della lettura di un intero da tastiera:

int a;
scanf(%d, &a);
Scrittura su file
Dopo la connessione del file in modalit scrttura, attraverso lidentificatore del file
possibile eseguire le operazioni di lettura che avvengono con lutilizzo della
funzione fprintf() della libreria stdio.h:

int fscanf(FILE*fid, char*format, var)

dove fid lidentificativo del file, format indica il tipo degli elementi da scrivere e
var la variabile in cui si trovano gli elementi da scrivere su file. La funzione
ritorna il numero di elementi effettivamente scritti, se viene restituito un valore
negativo significa che si sono verificati problemi di scrittura su file. La variabile
format pu assumere gli stessi valori illustrati per la lettura dei file. Di seguito
riportato un esempio di scrittura su file di un array di interi (sul primo rigo del file
di testo viene inserito il riempimento del vettore):

register int i;
FILE*fp;
int count;
int tot=0;
int riemp=3;
int vettore[]= {1,2,3};
if (!(fp=fopen("pippo.txt","w"))) {
printf("\n Il file non puo' essere salvato\n");
}
else {
count=fprintf(fp,"%d\n",riemp);
tot=tot+count;
for (i=0; i<riemp; i++) {
count=fprintf(fp,"%d\n",vettore[i]);
tot=tot+count;
}
}
if (tot==riemp+1) printf(Scrittura avvenuta con successo);
La scrittura di informazioni su standard output (monitor) avviene invece,
come gi descritto, attraverso la funzione pritnf() della libreria stdio.h:
int printf(char*format, var)
dove var rappresenta la variabile il cui contenuto sar visualizzato a video, mentre
format ha lo stesso significato dellomonima variabile della funzione printf. Di
seguito e riportato lesempio di scrittura di un intero su monitor:

int a;
printf(%d, a);
216 Capitolo sesto

La gestione delle stringhe


Il C possiede un ampio insieme di funzioni per il trattamento delle stringhe e
dei caratteri. Si ricorda che una stringa coincide con un array di caratteri terminato
col carattere \0. Tutte le dichiarazioni richieste dalle funzioni per il trattamento
delle stringhe sono contenute nel file header string.h.
Di seguito si mostrer lutilizzo solo di alcune delle funzioni per la gestione
delle stringhe, rimandando ad appositi manuali una trattazione pi approfondita.
La funzione strcat() permette il concatenamento di due stringhe:

char *strcat(char *str1, char *str2)

Tale funzione concatena una copia della stringa str2 alla stringa str1,
concludendo la stringa str1 (che conterr la stringa risultante) con un carattere di
terminazione. Il primo carattere di str2 si sovrappone al terminatore originale di
str1, mentre la stringa str2 rimane inalterata. La funzione restituisce un puntatore
nullo. Di seguito riportato un esempio per la scrittura a video della
concatenazione delle stringhe ciao e come stai?.

char s1[]={c,i,a,o,.,.,., \0};


char s2[]={c,o,m,e, ,s,t,a,i,?, \0};
strcat(s1,s2);
printf(%s,s1);

La funzione strcpy() permette la copia di stringhe:

char *strcpy (char *str1, char *str2)

Tale funzione copia il contenuto della stringa str2 nella stringa str1,
restituendo un puntatore a str1. Di seguito riportato un esempio per la scrittura a
video della copia di una stringa.

char str[100];
strcpy(str,ciao);
printf(%s,str);

La funzione strcmp() permette di verificare luguaglianza tra stringhe:

int strcmp(char *str1, char *str2)

Tale funzione confronta secondo le regole lessicografiche due stringhe str1 e


str2 terminate da carattere nullo e restituisce un intero il cui valore determinato
sulla base delle seguenti regole:
- 0 le stringhe sono uguali;
- minore di 0 le stringhe sono diverse ed in pi str1 ha meno caratteri
di str2;
- maggiore di 0 le stringhe sono diverse e str1 ha pi caratteri di str2.
Il linguaggio C 217

Di seguito riportato un esempio per la verifica se due stringhe sono uguali.

char s1[100];
char s2[100];
scanf(%s,s1);
scanf(%s,s2);
if (strcmp(s1,s2)==0) printf(stringhe uguali);

La funzione strlen() permette di determinare la lunghezze di una stringa:

int strlen(char *str1)

Tale funzione conta il numero di caratteri da cui composta una stringa str1
che termina col carattere di terminazione, escludendo tale carattere dal conteggio.
Di seguito riportato un esempio di conteggio di caratteri di una stringa.
char s1[100];
scanf(%s,s1);
int len=strlen(s1);
printf(stringa lunga %d caratteri,len);

Infine, le due funzioni:

char*strlwr(char *str1)
char*strupr(char *str1)

convertono rispettivamente una stringa str1 in minuscolo e maiuscolo, restituendo


entrambe un puntatore alla nuova stringa.
Funzioni per il calcolo matematico
Il C possiede unampia gamma di funzioni matematiche presenti allinterno della
libreria math.h. Tali funzioni hanno come argomenti di ingresso variabili di tipo
double e restituiscono come argomenti di uscita ancora variabili di tipo double.
Le funzioni si possono dividere nelle seguenti categorie:
- funzioni trigonometriche
o double sin(double x)
per il calcolo del seno di un numero reale x
o double cos(double x)
per il calcolo del coseno di un numero reale x
o double tan(double x)
per il calcolo del tangente di un numero reale x
- funzioni iperboliche
o double sinh(double x)
per il calcolo del seno iperbolico di un numero reale x
o double cosh(double x)
per il calcolo del coseno iperbolico di un numero reale x
o double tanh(double x)
per il calcolo del tangente iperbolica di un numero reale
x
- funzioni esponenziali e logaritmiche
218 Capitolo sesto

o double exp(double x)
per il calcolo dellesponenziale di un numero reale x
o double log10(double x)
per il calcolo del logaritmo in base 10 di un numero
reale x
o double log2(double x)
per il calcolo del logaritmo naturale di un numero reale
x
- altre funzioni
o double sqrt (double x)
per il calcolo della radice quadrata di un numero reale x
o double fabs(double x)
per il calcolo del valore assoluto di un numero reale x
o double ceil (double x)
per il calcolo dellintero pi piccolo non inferiore ad un
numero reale x
o double floor(double x)
per il calcolo dellintero pi grande non superiore ad un
numero reale x
o double fmod(double x, double y)
per il calcolo del resto in modulo della divsione di x per
y
o double pow(double base, double exp)
per il calcolo di baseexp

6.7. Gli algoritmi di base in C


Si vogliono ora di seguito mostrare degli algoritmi base dellinformatica realizzati
con lausilio del linguaggio C.
6.7.1. Lo scambio di valore
Descrizione del problema : Scrivere una funzione che effettui lo scambio di
valore tra due informazioni di un tipo T. Esempio:
input: x=3, y=5
output: x=5, y=3

Descrizione dellalgoritmo : Se si associano alle due informazioni due contenitori


di liquidi, il problema diventa quello del loro trasferimento da un contenitore
allaltro senza che i liquidi si mischiano. Risulta allora evidente che non c modo
di effettuare tale trasferimento se non introducendo un terzo contenitore che
permette di svuotare uno degli altri due contenitori. Si pu cos travasare il liquido
il liquido dellaltro in quello che si svuotato e infine ritrasferire in esso il liquido
che stato spostato per primo nel terzo contenitore. Si comprende allora che per
scambiare due informazioni dello stesso tipo necessario introdurre una terza
variabile, ovviamente dello stesso tipo. Ad esempio date le tre informazioni (tra
parentesi quadre riportato il valore delle variabili):
x [3] y [5] z[]
si copia dapprima x in z:
Il linguaggio C 219

x [3] y [5] z[3]


poi y in x:
x [5] y [5] z[3]
ed infine z in y:
x [5] y [3] z[3]
utilizzando la sintassi del C, lalgoritmo assume la seguente forma:
z=x;
x=y;
y=z;

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione scambia con le seguenti caratteristiche:
- Paramentri di input: [x,y] informazioni prima dello scambio
- Parametri di output: [x,y] informazioni dopo lo scambio
- Variabili locali: [z] informazione necessaria allo scambio

Implementazione
// FUNZIONE PER LO SCAMBIO DI VALORE
// La funzione void scambia(x,y)
// permette di scambiare il valore delle variabili x ed y

void scambia(float*x, float*y) {


// dichiarazione variabile locale di appoggio utilizzata per lo scambio
float z;

// effettua scambia per mezzo della variabile z


z=*x;
*x=*y;
*y=z;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
int main () {
int x,y;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma che effettua lo scambio di valore tra 2 numeri\n");
printf("Inserisci il primo numero x: ");
scanf("%f",&x);
printf("Inserisci il secondo numero y: ");
scanf("%f",&y);
printf("\nIl valore di x prima dello scambio e': %f\n",x);
printf("\nIl valore di y prima dello scambio e': %f\n",y);
printf("\nElaborazione in corso....\n");
scambia(&x,&y);
system("Pause");
printf("\nIl valore di x dopo lo scambio e': %f\n",x);
printf("\nIl valore di y dopo lo scambio e': %f\n",y);
system("Pause");
return 0;
}
220 Capitolo sesto

6.7.2. Inserimento in un vettore


Descrizione del problema: Scrivere una funzione per linserimento
dellinformazione info nella posizione posiz di un vettore di n elementi di un certo
tipo T. Esempio:
input: n=5, v= [10 50 20 40 35], info=66, posiz=3
output: n=6, v=[10 50 66 20 40 35]

Descrizione dellalgoritmo: Si pu pensare al vettore come ad uno scaffale di libri


allineati verso sinistra. Per far posto ad un nuovo libro in una posizione assegnata,
bisogna spostare tutti i libri che si trovano in posizione successiva a quella data di
una posizione verso destra. Lo spostamento pu essere fatto un libro alla volta,
cominciando da quello in ultima posizione. Dopo linserimento, il numero di libri o
riempimento dello scaffale aumentato di una unit. Si fa lipotesi che lo scaffale
non sia mai pieno.
Nel caso in cui si debba inserire la lettera a nella seconda posizione di un
vettore di 4 elementi la procedura per linserimento riportata di seguito:
[x, y, z, f]
si effettua lo spostamento (shift) di tutti gli elementi del vettore, a partire
dallultimo fino a quello in seconda posizione, di un posto a destra:
[x, , y, z, f]
si copia il nuovo elemento nella seconda posizione e si incrementa di una unit il
numero di elementi allinterno del vettore:
[x, a, y, z, f]
Utilizzando la sintassi del C, lalgoritmo assume la seguente forma :
for (i=n-1;i>=posiz;i--) {
v[i+1]=v[i];
}
v[posiz]=info;
n=n+1;

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione inserisci con le seguenti caratteristiche:
- Parametri di Input: [v,info,posiz,n] rispettivamente vettore, elemento da
inserire e rispettiva posizione, e riempimento del vettore
- Parametri di Output: [v,n] vettore e riempimento dopo linserimento
- Variabili locali: [i] contatore di ciclo

Implementazione
// FUNZIONE PER L'INSERIMENTO DI UN ELEMENTO IN UN VETTORE
// La funzione void inserisci(v,info,posiz,n)
// permette di inserire l'elemento info al posto posiz nel vettore v di riempimento n
// Vettore un alias di un tipo array di float

void inserisci(Vettore v, float info, int posiz, int* n) {

// contatore di ciclo
register int i;

// effettua l'inserimento mediante shift


Il linguaggio C 221

for (i=*n-1;i>=posiz;i--) {
v[i+1]=v[i];
}
v[posiz]=info;
// aggiorna il riempimento
*n=*n+1;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
int main () {
Vettore v;
register int j;
int posiz,n;
float info;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma di inserimento di un elemento in un vettore\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
};
printf("Inserisci elementi del Vettore (dopo ogni un numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%g",&v[j]);
}
printf("\nVettore v prima dell'inserimento\n");
printf("Vetore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%g,",v[j]);
else
printf("%g",v[j]);
}
printf("]\n");
printf("Inserisci l'elemento che vuoi inserire: ");
scanf("%f",&info);
posiz=-1;
while ((posiz<0) || (posiz>n-1)) {
printf("Inserisci la posizione dove inserirlo (>=0 <=%d): \n",n-1);
scanf("%d",&posiz);
};
printf("\nElaborazione in corso....\n");
inserisci(v,info,posiz,&n);
system("Pause");
printf("\nVettore v dopo l'inserimento\n");
printf("Vetore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%g,",v[j]);
else
printf("%g",v[j]);
}
222 Capitolo sesto

printf("]\n");
system("Pause");
return 0;
}

6.7.3. Eliminazione in un vettore


Descrizione del problema: Scrivere una funzione per leliminazione dellelemento
in una posizione posiz data di un vettore di n elementi di un certo tipo T. Esempio:
input: n=5, v= [22 50 30 16 10], posiz=2
output: n=4, v=[22 30 16 10]

Descrizione dellalgoritmo: Si pu pensare al vettore come ad uno scaffale di libri


allineati verso sinistra. Dopo avere tolto il libro dalla posizione assegnata, bisogna
spostare tutti quelli che si trovano alla sua destra di un posto verso sinistra per
recuperare lo spazio resosi disponibile. Lo spostamento viene fatto un libro alla
volta, cominciando da quello successivo al libro nella posizione assegnata e si
termina quando si arrivati allultimo libro. Dopo leliminazione, il numero di libri
o riempimento dello scaffale diminuito di una unit. Si fa lipotesi che lo scaffale
contenga almeno un elemento. Nel caso in cui si voglia eliminare lelemento in
seconda posizione di un vettore di 4 elementi, la procedura per leliminazione
riportata di seguito:
[x, y, z, f]
si effettua lo spostamento (shift) di tutti gli elementi del vettore, a partire da quello
in terza posizione fino allultimo, di un posto a sinistra:
[x, z, f]
si diminuisce di una unit il numero di elementi allinterno del vettore. Utilizzando
la sintassi del C, lalgoritmo assume la seguente forma :
for (i=posiz+1;i<n;i++) {
v[i-1]=v[i];
}
n=n-1;

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione elimina con le seguenti caratteristiche:
- Parametri di Input: [v,posiz,n] vettore di partenza, posizione
dellelemento da eliminare e riempimento del vettore
- Parametri di Output: [v,n] vettore e suo riempimento dopo leliminazione
- Variabili locali: i contatore di ciclo

Implementazione
// FUNZIONE PER L'ELIMINAZIONE DI UN ELEMENTO IN UN VETTORE
// La funzione void elimina(v, posiz)
// permette di eliminare l'elemento al posto posiz nel vettore v di riempimento n
// Vettore un alias di un tipo array di float

void elimina(Vettore v, int posiz, int *n) {

// contatore di ciclo
register int i;
Il linguaggio C 223

// effettua l'eliminazione mediante shift


for (i=posiz+1;i<*n;i++) {
v[i-1]=v[i];
}
// aggiorna il riempimento
*n=*n-1;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
int main () {
Vettore v;
register int j;
int posiz,n;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma di eliminazione di un elemento da un vettore\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
};
printf("Inserisci gli elementi del Vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%g",&v[j]);
}
printf("\nVettore v prima dell'eliminazione\n");
printf("Vetore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%g,",v[j]);
else
printf("%g",v[j]);
}
printf("]\n");
posiz=-1;
while ((posiz<0) || (posiz>n-1)) {
printf("Inserisci posizione dellelemento da eliminare (>=0 <=%d): \n",n-1);
scanf("%d",&posiz);
};
printf("\nElaborazione in corso....\n");
elimina(v,posiz,&n);
system("Pause");
printf("\nVettore v dopo l'eliminazione\n");
printf("Vetore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%g,",v[j]);
else
printf("%g",v[j]);
}
printf("]\n");
system("Pause");
return 0;
224 Capitolo sesto

6.7.4. Eliminazione di una colonna da una matrice


Descrizione del problema: Scrivere una funzione per leliminazione
dellelemento in una data colonna col di una matrice di n righe e m colonne di
elementi di un certo tipo T. Esempio:
input: n=3, m=4, A= [10 22 33 50; 20 54 80 41; 30 10 23 31], col=2
output: n=3, m=3, A=[10 33 50; 20 80 41; 30 23 31]

Descrizione dellalgoritmo: Leliminazione di una colonna in una matrice si


effettua applicando lalgoritmo di eliminazione di un elemento da un vettore a tutte
le righe della matrice. Utilizzando la sintassi del C, lalgoritmo assume la seguente
forma:
for (i=0;i<n;i++) {
for (j=col+1;j<m;j++) {
A[i][j-1]=A[i][j];
}
}
m=m-1;

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione eliminacol con le seguenti caratteristiche:
- Parametri di Input: [A,n,m,col], matrice, numero di righe, numero di
colonne ed indice della colonna da eliminare
- Parametri di Output: [A,m] matrice e numero di colonne dopo
leliminazione
- Variabili locali: i, j indici di riga e colonna

Implementazione
// FUNZIONE PER L'ELIMINAZIONE DI UNA COLONNA DA UNA MATRICE
// La funzione void eliminacol(A,n,m,col)
// permette di eliminare gli elementi della colonna col dalla matrice A
// Matrice un alias di un tipo array bidimensionale di int

void eliminacol(Matrice A, int n, int* m, int col) {

// contatori di ciclo
register int i,j;

// effettua l'eliminazione mediante shift


for (i=0;i<n;i++) {
for (j=col+1;j<*m;j++) {
A[i][j-1]=A[i][j];
}
}
// aggiorna il riempimento relativo alle colonne
*m=*m-1;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
Il linguaggio C 225

// Funzione main
int main () {
Matrice A;
register int k,z;
int n,m,col;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma che elimina una colonna da una matrice\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero di righe della matrice(>=0 <=50): \n");
scanf("%d",&n);
};
m=-1;
while ((m<0) || (m>MMAX-1)) {
printf("Inserisci il numero di colonne della matrice(>=0 <=50): \n");
scanf("%d",&m);
};
printf("Inserisci gli elementi della Matrice (dopo ogni elem premere INVIO)\n");
for (k=0; k<n; k++) {
for (z=0; z<m; z++) {
printf("elemento[%d,%d]=",k,z);
scanf("%d",&A[k][z]);
}
}
printf("\nMatrice A prima dell'eliminazione\n");
printf("A=\n|");

for (k=0; k<n; k++) {


for (z=0; z<m; z++) {
if (z<m-1) printf("%d ",A[k][z]);
else printf("%d|",A[k][z]);
}
if (k<n-1) printf("\n|");
else printf("\n");
}
col=-1;
while ((col<0) || (col>m-1)) {
printf("Inserisci l'indice di colonna da eliminare (>=0 <=%d): \n",m-1);
scanf("%d",&col);
};
printf("\nElaborazione in corso....\n");
eliminacol(A,n,&m,col);
system("Pause");
printf("\nMatrice A dopo l'eliminazione\n");
printf("A=\n|");
for (k=0; k<n; k++) {
for (z=0; z<m; z++) {
if (z<m-1) printf("%d ",A[k][z]);
else printf("%d|",A[k][z]);
}
if (k<n-1) printf("\n|");
else printf("\n");
}
system("Pause");
return 0;
}
226 Capitolo sesto

6.7.5. Eliminazione di una riga da una matrice


Descrizione del problema Scrivere una funzione per leliminazione dellelemento
in una data riga riga di una matrice di n righe e m colonne di elementi di un certo
tipo T. Esempio:
input: n=4, m=3, A= [10 22 33; 20 54 80; 30 10 23; 22 10 8], riga=2
output: n=3, m=3, A=[10 22 33; 30 19 23; 22 10 8]

Descrizione dellalgoritmo: Leliminazione di una colonna in una matrice si


effettua applicando lalgoritmo di eliminazione di un elemento da un vettore a tutte
le colonne della matrice. Utilizzando la sintassi del C, lalgoritmo assume la
seguente forma:
for (j=0;j<m;j++) {
for (i=riga+1;i<n;i++) {
A[i-1][j]=A[i][j];
}
}
n=n-1;

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione eliminariga con le seguenti caratteristiche:
- Parametri di Input: [A,n,m,riga] matrice, numero di righe, numero di
colonne ed indice della riga da eliminare
- Parametri di Output: [A,n] matrice e numero di righe dopo leliminazione
- Variabili locali: i, j indici di riga e colonna

Implementazione

// FUNZIONE PER L'ELIMINAZIONE DI UNA RIGA DA UNA MATRICE


// La funzione void eliminacol(A,n,m,riga)
// permette di eliminare gli elementi di una data riga dalla matrice A
// Matrice un alias di un tipo array bidimensionale di int

void eliminariga(Matrice A, int* n, int m, int riga) {

// contatori di ciclo
register int i,j;

// effettua l'eliminazione mediante shift


for (j=0;j<m;j++) {
for (i=riga+1;i<*n;i++) {
A[i-1][j]=A[i][j];
}
}
// aggiorna il riempimento relativo alle colonne
*n=*n-1;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
Il linguaggio C 227

int main () {
Matrice A;
register int k,z;
int n,m,riga;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma di eliminazione di una riga da una matrice\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero di righe della matrice(>=0 <=50): \n");
scanf("%d",&n);
};
m=-1;
while ((m<0) || (m>MMAX-1)) {
printf("Inserisci il numero di colonne della matrice(>=0 <=50): \n");
scanf("%d",&m);
};
printf("Inserisci gli elementi della Matrice (dopo ogni elem premere INVIO)\n");

for (k=0; k<n; k++) {


for (z=0; z<m; z++) {
printf("elemento[%d,%d]=",k,z);
scanf("%d",&A[k][z]);
}
}
printf("\nMatrice A prima dell'eliminazione\n");
printf("A=\n|");
for (k=0; k<n; k++) {
for (z=0; z<m; z++) {
if (z<m-1) printf("%d ",A[k][z]);
else printf("%d|",A[k][z]);
}
if (k<n-1) printf("\n|");
else printf("\n");
}
riga=-1;
while ((riga<0) || (riga>m-1)) {
printf("Inserisci l'indice di riga da eliminare (>=0 <=%d): \n",m-1);
scanf("%d",&riga);
};
printf("\nElaborazione in corso....\n");
eliminacol(A,&n,m,riga);
system("Pause");
printf("\nMatrice A dopo l'eliminazione\n");
printf("A=\n|");
for (k=0; k<n; k++) {
for (z=0; z<m; z++) {
if (z<m-1) printf("%d ",A[k][z]);
else printf("%d|",A[k][z]);
}
if (k<n-1) printf("\n|");
else printf("\n");
}
system("Pause");
return 0;
}
228 Capitolo sesto

6.7.6. Ricerca sequenziale


Descrizione del problema: Scrivere una funzione per la ricerca dellinformazione
info in un vettore non ordinato avente n elementi di un certo tipo T. In particolare
la funzione deve produrre una indicazione sullesistenza dellinformazione nel
vettore e, nel caso esista, la posizione. Si fa lipotesi che nel caso in cui lelemento
non sia presente nel vettore la sua posizione -1. Esempio:
input: v= [9 5 6 8 7], info=8
output: msg=elemento presente, posiz=4

Descrizione dellalgoritmo: La ricerca di un oggetto in un insieme si effettua


fissando la strategia con la quale si possono effettuare i confronti delloggetto
cercato con quelli dellinsieme. Se non si ha una conoscenza dellinsieme, lunico
modo di procedere quello di prendere un oggetto alla volta e confrontarlo con
quello dato fino a quando non se ne trova uno uguale ad esso o stato visionato
lintero insieme. Tale tipo di ricerca detto sequenziale. La ricerca di
uninformazione in un vettore che contiene valori tra loro non ordinati procede in
maniera sequenziale. Si comincia a confrontare il primo elemento con quello
ricercato. Poi il secondo, poi il terzo e cos via fin quando il confronto non risulta
verificato. In questo modo si verifica lassenza dellelemento dopo averlo
confrontato con tutti gli elementi del vettore. Nel caso contrario, quando lo si
incontra, si ferma lindagine facendo lipotesi che nel vettore non esistano valori
tra di loro uguali. Facciamo ora un esempio. Sia assegnato il vettore:
v=[9 5 6 8 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore) e sia 4
il valore da ricercare. Allora lalgoritmo procede iterativamente nel seguente
modo:
passo confronto corrente trovato fine vettore
(posizione nel vettore)
1 4==9 no no
2 4==5 no no
3 4==6 no no
4 4==8 no no
5 4==9 no si

e la ricerca termina con lindicazione che 4 non presente nel vettore.


Cerchiamo quindi il valore 8:

passo confronto corrente trovato fine vettore


(posizione nel vettore)
1 8==9 no no
2 8==5 no no
3 8==6 no no
4 8==8 si no
Il linguaggio C 229

e la ricerca termina con lindicazione che il valore 8 presente allinterno del


vettore nella posizione 4. Utilizzando la sintassi del C, lalgoritmo assume la
seguente forma:

trovato=0;
i=0;
posiz=-1;
while (!trovato && i<n) {
if (v[i]==info) {
trovato=1;
posiz=i;
}
i++;
}

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione ricercaseq con le seguenti caratteristiche:
- Parametri di Input: [v,n,info] vettore dingresso, suo riempimento ed
informazione da ricercare
- Parametri di Output: [posiz] posizione dellelemento allinterno del vettore
- Variabili locali: [i,trovato,posiz] contatore di ciclo, variabile binaria che
indica se il confronto ha avuto successo e posizione dellelemento
allinterno del vettore

Implementazione
// FUNZIONE PER LA RICERCA DI UN ELEMENTO IN UN VETTORE
// La funzione int ricercaseq(v,n,info)
// permette di ricerca la posizione dellelemento info nel vettore v
// Vettore un alias di un tipo array monodimensionale di int

int ricercaseq(Vettore v, int n, int info) {

// contatore di ciclo, posizione dellelemento e variabile darresto


register int i=0;
int posiz=-1;
int trovato=0;

// effettua la ricerca sequenziale scorrendo gli elementi del vettore


while (!trovato && i<n) {
if (v[i]==info) {
trovato=1;
posiz=i;
}
i++;
}
// ritorna la posizione dellelemento
return posiz;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
230 Capitolo sesto

int main () {
Vettore v;
register int j;
int posiz,info,n;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma per la ricerca di un elemento da un vettore\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
};
printf("Inserisci gli elementi del Vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%d",&v[j]);
}
printf("\nVettore v prima della ricerca\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
printf("Inserisci l'elemento da ricercare: ");
scanf("%d",&info);
printf("\nElaborazione in corso....\n");
posiz=ricercaseq(v,n,info);
system("Pause");
printf("\n- Risultati ricerca -\n");
if (posiz!=-1) printf("L'elemento %d e' presente in posizione %d\n",info,posiz);
else printf("L'elemento % d non e presente nel vettore v\n",info);
system("Pause");
return 0;
}

6.7.7. Ricerca binaria


Descrizione del problema: Scrivere una funzione per la ricerca dellinformazione
info in un vettore non ordinato avente n elementi di un certo tipo T. In particolare
la funzione deve produrre una indicazione sullesistenza dellinformazione nel
vettore e, nel caso esista, la posizione. Si fa lipotesi che nel caso in cui lelemento
non sia presente nel vettore la sua posizione -1. Esempio:
input: v= [9 5 6 8 7], info=8
output: msg=elemento presente, posiz=4

Descrizione dellalgoritmo: La ricerca di una informazione in un elenco si pu


effettuare come visto nel caso precedente confrontando uno dopo laltro i valori
dellelenco con quello cercato fino a quando non si trova un elemento uguale o non
si analizzato lintero elenco. Il metodo sequenziale richiede nel caso peggiore n
confronti, dove n il riempimento del vettore. Se lelenco ordinato, si ricorre
allora ad una ricerca che ad ogni passo riduce linsieme in cui cercare mediante
unopportuna tecnica di dimezzamento. In questo modo il caso peggiore richiede al
Il linguaggio C 231

pi log2(n) confronti. Il metodo noto col nome di ricrca binaria si basa sui seguenti
passi:
- determinazione del punto medio dellinsieme in cui cercare;
- confronto dellelemento in questa posizione con quello da cercare;
- individuazione dellinsieme in cui continuare la ricerca se il passo 2 non
ha successo; esso risulta per vettore ordinato in senso crescente: il
sottoinsieme degli elementi in posizioni successive al punto medio se il
valore da cercare maggiore di quello del punto medio, altrimenti quello
caratterizzato da posizioni inferiori;
- ripetizioni dei passi precedenti finch il passo 2 non risulti verificato o
non sia possibile fissare un sottoinsieme in cui continuare la ricerca.
Facciamo ora un esempio. Sia assegnato il vettore:
v=[15 22 29 36 50 55]
(1 2 3 4 5 6)

(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore) e
sia 21 il valore da ricercare. Allora lalgoritmo procede iterativamente nel seguente
modo:

passo punto confronto prossimo fine trovato


medio corrente sottoinsieme di
ricerca
1 29 21<29 [15, 22] no no
2 15 21>15 [22] no no
3 22 21<22 [22, 15] si no

A questo punto la ricerca termina perch non esiste un sottoinsieme (lestremo


inferiore maggiore del superiore) in cui continuare la ricerca, con lindicazione
che 21 non presente nel vettore. Cerchiamo quindi il valore 50:

passo punto confronto prossimo fine trovato


medio corrente sottoinsieme di
ricerca
1 29 50>29 [36, 50, 55] no no
2 50 50==50 Vuoto si si
3 22 21<22 [22, 15] si no

e la ricerca termina con lindicazione che il valore 50 presente allinterno del


vettore nella posizione 5. Utilizzando la sintassi del C, lalgoritmo assume la
seguente forma:
posiz=-1;
trovato=0;
ei=0;
es=n-1;
while (!trovato && ei<es) {
medio=(ei+es)/2;
if (info==v[medio]) {
trovato=1;
posiz=medio;
232 Capitolo sesto

}
if (info<v[medio]) es=medio-1;
else ei=medio+1;
}

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione ricercabin con le seguenti caratteristiche:
- Parametri di Input: [v,n,info] vettore dingresso, suo riempimento ed
informazione da ricercare
- Parametri di Output: [posiz] posizione dellelemento allinterno del
vettore
- Variabili locali: [trovato,posiz,medio,ei,es] variabile binaria che indica se
il confronto ha avuto successo, posizione dellelemento allinterno del
vettore, punto, medio, estremo inferiore ed estemo superiore
dellintervallo di ricerca

Implementazione
// FUNZIONE PER LA RICERCA BINARIA DI UN ELEMENTO IN UN VETTORE
// La funzione int ricercaseq(v,n,info)
// permette di ricerca la posizione dellelemento info nel vettore v
// Vettore un alias di un tipo array monodimensionale di int

int ricercabin(Vettore v, int n, int info) {


// punto medio, etremi di ricerca, posizione dell'elemento e variabile d'arresto
int ei,es,medio;
int posiz=-1;
int trovato=0;
// inzializza l'intervallo di ricerca
ei=0;
es=n-1;
// effettua la ricerca binaria aggiornando gli intervalli di ricerca
while (!trovato && ei<es) {
medio=(ei+es)/2;
if (info==v[medio]) {
trovato=1;
posiz=medio;
}
if (info<v[medio]) es=medio;
else ei=medio;
}
// ritorna la posizione dell'elemento
return posiz;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
int main () {
Vettore v;
register int j;
int posiz,info,n;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma per la ricerca di un elemento da un vettore\n");
Il linguaggio C 233

n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
};
printf("Inserisci gli elementi del vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%d",&v[j]);
}
printf("\nVettore v prima della ricerca\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
printf("Inserisci l'elemento da ricercare: ");
scanf("%d",&info);
printf("\nElaborazione in corso....\n");
posiz=ricercabin(v,n,info);
system("Pause");
printf("\n- Risultati ricerca -\n");
if (posiz!=-1) printf("L'elemento %d e' presente in posizione %d\n",info,posiz);
else printf("L'elemento % d non e' presente nel vettore v\n",info);
system("Pause");
return 0;
}

6.7.8. La ricerca del valore massimo in un vettore


Descrizione del problema: Scrivere una funzione che determini il massimo di un
vettore v formato da n elementi di un certo tipo T. Esempio:
input: v=[10 55 20 11 30]
output: max=55

Descrizione dellalgoritmo: Lindividuazione del massimo in un insieme si


effettua osservando uno dopo laltro gli elementi che lo compongono e
memorizzando di volta in volta il valore pi grande. In particolare, si inizia
facendo lipotesi che il massimo sia il primo elemento del vettore e
successivamente lo si confronta con i restanti valori del vettore. Ogni volta che si
incontra un valore pi grande, si effettua laggiornamento del massimo. Facciamo
un esempio. Sia assegnato il vettore:

v=[3 5 6 8 7]
(1 2 3 4 5)

(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore). Allora
lalgoritmo procede iterativamente nel seguente modo:

massimo posizione confronto esito aggiornamento


234 Capitolo sesto

vettore (max<elemento confronto massimo


corrente del
vettore)
max=3 2 3<5 vero si
max=5 3 5<6 vero si
max=6 4 6<8 vero si
max=8 5 8<7 falso no

Utilizzando la sintassi del C, lalgoritmo assume la seguente forma :


max=v[0];
for (i=1;i<n;i++) {
if (v([i]>max) max=v[i];
}

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione max con le seguenti caratteristiche:
- Parametri di Input: [v,n] vettore dingresso e suo riempimento
- Parametri di Output: [max] variabile contenete il massimo del vettore
- Variabili locali: [i,max] contatore di ciclo e massimo corrente
Implementazione
// FUNZIONE PER LA RICERCA DEL MASSIMO DI UN VETTORE
// La funzione Elem max(v,n)
// permette di ricercare il massimo tra gli elementi di un vettore v
// Vettore un alias di un tipo array monodimensionale di int
// Elem un alias di un tipo intero rappresentante il tipo degli elemento del vettore

Elem max(Vettore v, int n) {

// contatore di ciclo e massimo corrente


register int i;
Elem max;
// inzializza il massimo
max=v[0];
// effettua la ricerca del massimo scorrendo gli elementi del vettore
for (i=1;i<n;i++) {
if (v[i]>max) max=v[i];
}
// ritorna il massimo
return max;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
int main () {
Vettore v;
register int j;
int n;
Elem massimo;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma per la ricerca del massimo vettore\n");
n=-1;
Il linguaggio C 235

while ((n<0) || (n>NMAX-1)) {


printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
};
printf("Inserisci gli elementi del vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%d",&v[j]);
}
printf("\nVettore v prima della ricerca\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
printf("\nElaborazione in corso....\n");
massimo=max(v,n);
system("Pause");
printf("\nIl massimo del vettore e' %d\n",massimo);
system("Pause");
return 0;
}

6.7.9. La posizione del valore minimo in un vettore


Descrizione del problema: Scrivere una funzione che determini la posizione del
valore minimo di un vettore formato da n elementi di un certo tipo T. Esempio:
input: v=[10 55 20 11 30]
output: posiz=1

Descrizione dellalgoritmo: Lindividuazione della posizione del minimo in un


insieme si effettua osservando uno dopo laltro gli elementi che lo compongono e
memorizzando di volta in volta la posizione dellelemento avente il valore pi
piccolo. In particolare, si inizia facendo lipotesi che la posizione del minimo sia
quella del primo elemento del vettore. Successivamente si confronta lelemento
nella presunta posizione di minimo con i restanti valori del vettore. Ogni volta che
si incontra un valore pi piccolo, si effettua laggiornamento della posizione in
modo che alla fine dei confronti si abbia la posizione del minimo. Si fa lipotesi
che nel vettore non esistano elementi uguali. Facciamo ora un esempio.
Sia assegnato il vettore:
v=[9 5 6 3 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore). Allora
lalgoritmo procede iterativamente nel seguente modo:
posiz. posizione confronto esito aggiornamento
minimo vettore (min>elemento confronto posizione
corrente del minimo
vettore)
posiz=1 2 9>5 Vero si
236 Capitolo sesto

posiz=2 3 5>6 Falso no


posiz=2 4 5>3 Vero si
posiz=4 5 3>7 Falso no

Utilizzando la sintassi del C, lalgoritmo assume la seguente forma:


posiz=0;
for (i=1;i<n;i++) {
if (v([i]<v[posiz]) posiz=i;
}

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione posmin con le seguenti caratteristiche:
- Parametri di Input: [v,n] vettore dingresso e suo riempimento
- Parametri di Output: [posiz] variabile contenete la posizione del minimo
del vettore
- Variabili locali: [i,posiz] contatore di ciclo e posizione del minimo
corrente

Implementazione
// FUNZIONE PER LA RICERCA DEL MASSIMO DI UN VETTORE
// La funzione int posmin(v,n)
// permette di ricercare la posizione del minimo tra gli elementi di un vettore v
// Vettore un alias di un tipo array monodimensionale di int

int posmin(Vettore v, int n) {

// contatore di ciclo e posizione del minimo correnye


register int i;
int posiz;
// inzializza la posizione del minimo
posiz=0;
// effettua la ricerca del massimo scorrendo gli elementi del vettore
for (i=1;i<n;i++) {
if (v[i]<v[posiz]) posiz=i;
}
// ritorna la posizione del minimo
return posiz;
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
int main () {
Vettore v;
register int j;
int n, posiz;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma per la ricerca della posizione del minimo\n");
n=-1;
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
Il linguaggio C 237

};
printf("Inserisci gli elementi del vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%d",&v[j]);
}
printf("\nVettore v prima della ricerca\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
printf("\nElaborazione in corso....\n");
posiz=posmin(v,n);
system("Pause");
printf("\nLa posizione del minimo del vettore e' %d\n",posiz);
system("Pause");
return 0;
}

6.7.10. Ordinamento di un vettore col metodo della selezione


Descrizione del problema: Scrivere una funzione per lordinamento in senso
crescente di un vettore di n elementi di un certo tipo. Esempio:
input: v=[30 10 50 12 22]
output: v=[10 12 22 30 50]

Descrizione dellalgoritmo: Ordinare un vettore in senso crescente significa


imporre che scelti due qualsiasi indici i e j, tali che i<j risulti v(i)<v(j). Un
meccanismo di ordinamento consiste nel dividere linsieme da ordinare in due
parti: una ordinata ed una disordinata. Si procede allora estraendo un elemento alla
volta dallinsieme disordinato e accodandolo a quello ordinato in modo che
lordinamento non venga alterato. Allinizio linsieme ordinato vuoto e
lalgoritmo termina quando contiene tutti gli elementi del vettore. Vi sono vari
modi di estrarre lelemento dallinsieme disordinato. Quello che di seguito
presentiamo seleziona ad ogni passo lelemento da accodare. In particolare
seleziona il minimo dallinsieme disordinato e lo accoda allinsieme ordinato. In tal
modo si viene ad ogni passo a scegliere il valore pi grandi di quelli che lo
precedono e contemporaneamente pi piccolo di quelli che lo seguono. Facciamo
ora un esempio.
Sia assegnato il vettore:

v=[9 2 1 4 7]
(1 2 3 4 5)
(tra le parentesi tonde sono indicate le posizioni degli elementi nel vettore). Allora
lalgoritmo procede iterativamente nel seguente modo:

Parte Parte posizione accodamento nuovo


238 Capitolo sesto

Ordinata Disordinata minimo intervallo di


(valore ricerca del
minimo) minimo
Vuota (1 2 3 4 5) 3(1) [1] [9 2 4 7]
(1) (2 3 4 5) 2(2) [1 2] [9 4 7]
(1 2) (3 4 5) 4(4) [1 2 4] [9 7]
(1 2 3) (4 5) 5(7) [1 2 4 7] [9]
(1 2 3 4) (5) 5(9) [1 2 4 7 9] vuoto

Si noti che lalgoritmo termina quando linsieme disordinato si riduce ad un


unico elemento. Inoltre poich si usa lo stesso vettore per la parte ordinata e
disordinata, laccodamento viene effettuato scambiando di posto il minimo ed il
valore che occupa la posizione di accodamento. Utilizzando la sintassi del C,
lalgoritmo assume la seguente forma :
for (i=0;i<n;i++) {
imin=i;
for (k=i+1;k<n;k++) {
if (v[k]<v[imin]) imin=k;
}
temp=v[i];
v[i]=v[imin];
v[imin]=temp;
}

Descrizione delle funzioni: Per la realizzazione dellalgoritmo si utilizza una sola


funzione ordina con le seguenti caratteristiche:
- Parametri di Input: [v,n] vettore dingresso e suo riempimento
- Parametri di Output: [v] vettore ordinato
- Variabili locali: [i,k,temp,imin] contatori di ciclo, variabile dappoggio
per gli scambi e posizione del minimo corrente della parte disordinata

Implementazione
// FUNZIONE PER L'ORDINAMENTO DI UN VETTORE
// La funzione void ordina(v,n) permette di ordinare in maniera crescente gli elementi del
// vettore v
// Vettore un alias di un tipo array monodimensionale di int

void ordina(Vettore v, int n) {


// contatori di ciclo
register int i,k;
// indice contenente la posizione del minimo della parte disordinata
// e variabile d'appoggio per gli scambi
int imin, temp;
// effettua l'ordinamento col metodo della selezione
for (i=0;i<n;i++) {
imin=i;
for (k=i+1;k<n;k++) {
if (v[k]<v[imin]) imin=k;
}
// effettua lo scambio tra l'elemento corrente ed il minimo della parte disord
temp=v[i];
Il linguaggio C 239

v[i]=v[imin];
v[imin]=temp;
}
}

Esempio duso: Per testare la funzione sviluppata pu essere utilizzato il seguente


main in cui inserita anche la gestione dellI/O.
// Funzione main
int main () {
Vettore v; register int j; int n=-1;
printf("Benvenuti al corso di Fondamenti di Informatica\n");
printf("Esempio di programma per lordinamento di un vettore\n");
while ((n<0) || (n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",&n);
};
printf("Inserisci gli elementi del vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<n; j++) {
printf("elemento[%d]=",j);
scanf("%d",&v[j]);
}
printf("\nVettore v prima dell'ordinamento\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
printf("\nElaborazione in corso....\n");
ordina(v,n);
system("Pause");
printf("\nVettore v dopo l'ordinamento\n");
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%d,",v[j]);
else
printf("%d",v[j]);
}
printf("]\n");
system("Pause");
return 0;
}

6.8. Esempi di programmi completi in C


Si vogliono ora di seguito mostrare degli esempi di programmi completi realizzati
con lausilio del linguaggio C.
240 Capitolo sesto

6.8.1. Gestione di un array


Descrizione del problema: Scrivere una libreria di funzioni che permettano la
gestione di un array di elementi di un dato tipo T. In particolare si vogliono
realizzare delle funzioni per:
- lordinamento dellarray
- la ricerca del massimo
- la ricerca sequenziale di un dato elemento nellarray
- linput da tastiera degli elementi dellarray
- loutput su video degli elementi di un array
- la gestione di un men per lattivazione delle funzioni

Descrizione dellalgoritmo: Per la realizzazione delle funzioni si sfruttano gli


algoritmi visti nella sezione precedente.

Descrizione delle funzioni: Per la realizzazione delle funzioni si utilizzano le


seguenti funzioni, di cui riportiamo per semplicit solo i parametri di
ingresso/uscita:
- ordina
o paramentri di input: [v,n] vettore e riempimento
o parametri di output: [v] vettore ordinato
- max
o paramentri di input: [v,n] vettore e riempimento
o parametri di output: [max] elemento massimo del vettore
- ricercaseq
o paramentri di input: [v,n,info] vettore, riempimento e elemento
da ricercare
o parametri di output: [posiz] posizione dellelemento ricercato
nellarray
- input_array
o paramentri di input: nessuno
o parametri di output [v] vettore e suo riempimento
- output_array
o paramentri di input: [v,n] vettore e riempimento
o parametri di output: nessuno
- menu
o parametri di input: nessuno
o parametri di output: nessuno

Implementazione ed esempio duso: Di seguito riportato limplementazione


dellintero programma con il codice relativo alle varie funzioni ed al main
utilizzato per testare il programma.
// Gestione array

// Preambolo del programma

// Direttive di compilazione
#include <stdio.h>
#include <stdlib.h>
Il linguaggio C 241

# define NMAX 50

// Alias di tipi
typedef float Vettore [NMAX];
typedef float Elem;

// prototipi funzioni
void ordina (Vettore v, int n);
int ricercaseq (const Vettore v, int n, Elem info);
Elem max (const Vettore v, int n);
void input_array (Vettore v, int* n);
void output_array (const Vettore v, int n);
void menu();

// FUNZIONE PER L'ORDINAMENTO DI UN VETTORE


// La funzione void ordina(v,n)
// permette di ordinare in maniera crescente gli elementi del vettore v

void ordina(Vettore v, int n) {


// contatori di ciclo
register int i,k;
// indice contenente la posizione del minimo della parte disordinata
// e variabile d'appoggio per gli scambi
int imin;
Elem temp;

// effettua l'ordinamento col metodo della selezione


for (i=0;i<n;i++) {
imin=i;
for (k=i+1;k<n;k++) {
if (v[k]<v[imin]) imin=k;
}
// effettua lo scambio tra l'elemento corrente ed il minimo della parte
disordinata
temp=v[i];
v[i]=v[imin];
v[imin]=temp;
}
}

// FUNZIONE PER LA RICERCA DI UN ELEMENTO IN UN VETTORE


// La funzione int ricercaseq(v,n,info)
// ritona la posizione dell'elemento info nel vettore v
// se l'elemento non presente viene ritornato -1

int ricercaseq(const Vettore v, int n, Elem info) {


// contatore di ciclo, posizione dell'elemento e variabile d'arresto
register int i=0;
int posiz=-1;
int trovato=0;
// effettua la ricerca sequenziale scorrendo gli elementi del vettore
while (!trovato && i<n) {
if (v[i]==info) {
trovato=1;
posiz=i;
242 Capitolo sesto

}
i++;
}
// ritorna la posizione dell'elemento
return posiz;
}

// FUNZIONE PER LA RICERCA DEL MASSIMO DI UN VETTORE


// La funzione Elem max(v,n)
// ritorna il massimo tra gli elementi di un vettore v

Elem max(const Vettore v, int n) {


// contatore di ciclo e massimo corrente
register int i;
Elem max;
// inzializza il massimo
max=v[0];
// effettua la ricerca del massimo scorrendo gli elementi del vettore
for (i=1;i<n;i++) {
if (v[i]>max) max=v[i];
}
// ritorna il massimo
return max;
}

// FUNZIONE PER L'INPUT DI UN VETTORE


// La funzione input_array(v,n)
// gestisce l'input da tastiera degli elementi di un vettore v

void input_array(Vettore v, int* n) {


register int j;
*n=-1;
while ((*n<0) || (*n>NMAX-1)) {
printf("Inserisci il numero delle componenti del tuo vettore(>=0 <=50): \n");
scanf("%d",n);
};
printf("Inserisci gli elementi del vettore (dopo ogni numero premere INVIO)\n");
for (j=0; j<*n; j++) {
printf("elemento[%d]=",j);
scanf("%g",&v[j]);
}
}

// FUNZIONE PER L'OUTPUT DI UN VETTORE


// La funzione output_array(v,n)
// gestisce l'output su monitor degli elementi di un vettore v

void output_array(const Vettore v, int n) {


register int j;
printf("Vettore = [");
for (j=0; j<n; j++) {
if (j<n-1)
printf("%g,",v[j]);
Il linguaggio C 243

else
printf("%g",v[j]);
}
printf("]\n");
}

// FUNZIONE PER LA GESTIONE DELL'ATTIVAZIONE DELLE FUNZIONI


// La funzione menu()
// gestisce l'attivazione delle funzioni mediante un men interattivo

void menu () {
Vettore v;
int n, posiz;
Elem massimo, info;
char opzione;
n=0;
while (opzione!='6') {
printf ("\n---PROGRAMMA PER LA GESTIONE DI UN ARRAY---\n");
printf ("[1] Inserimento elementi del Vettore\n");
printf ("[2] Visualizzione elementi del Vettore\n");
printf ("[3] Ricerca elemento nel Vettore\n");
printf ("[4] Visualizza elemento massimo nel Vettore\n");
printf ("[5] Ordina elementi del Vettore\n");
printf ("[6] Esci\n");
scanf("%s",&opzione);

switch (opzione) {
case '1':
input_array(v,&n);
break;
case '2':
if (n!=0) output_array (v,n);
else printf("\nVettore vuoto\n");
break;
case '3':
printf("\nInserisci l'elemento da ricercare: ");
scanf("%g",&info);
posiz=ricercaseq(v,n,info);
if (posiz!=-1)
printf("\nL'elemento %g e' in posizione %d\n",info,posiz);
else printf("\nL'elemento % g non e' presente nel vettore\n",info);
break;
case '4':
massimo=max(v,n);
printf("\nIl massimo del vettore e' %g\n",massimo);
break;
case '5':
ordina(v,n);
break;
case '6':
printf("Uscita programma...\n");
system("PAUSE");
default:
printf("\nOpzione non supportata\n");
}
244 Capitolo sesto

}
}

// MAIN

int main() {
menu();
return 0;
}

6.8.2. Gestione di un archivio


Descrizione del problema: Scrivere una libreria di funzioni che permettano la
gestione di un archivio di di impiegati. Dove un impiegato pu essere visto come
unistanza di un tipo strutturato caratterizzato dalle seguenti informazioni:
- matricola
- nome
- cognome
- dipartimento di afferenza
In particolare si vogliono realizzare delle funzioni per:
- il caricamento da file dellarchivio
- il salvataggio sul file dellarchivio
- linserimento di un nuovo impiegato in archivio
- la visualizzazione del contenuto dellarchivio
- la ricerca di un impiegato per cognome
- la modifica del dipartimento di afferenza di un dato impiegato
Descrizione dellalgoritmo: Gli algoritmi necessari alla realizzazione delle
funzioni si riconducono a quelle visti nella sezione precedente.
Descrizione delle funzioni: Si utilizzano le seguenti funzioni, di cui riportiamo per
semplicit solo i parametri di ingresso/uscita:
- carica_archivio
o paramentri di input [f]: nome del file su cui risiede larchivio
o parametri di output: [imp,n] archivio con impiegati
- salva_archivio
o paramentri di input [imp,f]: archivio corrente e nome del file su
cui deve risiedere larchivio
o parametri di output: nessuno
- inserisci_impiegato
o paramentri di input: [imp] archivio corrente
o parametri di output: [imp] archivio aggiornato
- visualizza_archivio
o paramentri di input [imp]: archivio corrente
o parametri di output: nessuno
- ricerca_impiegato
o paramentri di input: [imp,cogn] archivio corrente e cognome
dellimpiegato da ricercare
o parametri di output [trovato]: variabile binaria che indica la
presenza dellimpiegato in archivio
- modifica_impiegato
Il linguaggio C 245

o paramentri di input: [imp,cogn,dip] archivio corrente, cognome


dellimpiegato da ricercare e nome del nuovo dipartimento
o parametri di output [imp]: nuovo archivio

Implementazione ed esempio duso: Di seguito riportato limplementazione


dellintero programma con il codice relativo alle varie funzioni ed al main
utilizzato per testare il programma.
// Gestione Archivio
// Preambolo
// direttive di compilazione
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define DIM_MAX 100
#define STR_MAX 100
//Alias di tipi
typedef char Stringa [STR_MAX];
typedef struct Impiegato {
int matricola;
Stringa Nome;
Stringa Cognome;
Stringa Dipartimento;
};
typedef Impiegato Impiegati [DIM_MAX];
// variabile globale contenente il riempimento dell'archivio
int num_impiegati=0;
// inserisce un nuovo impiegato in archivio
void inserisci_impiegato(Impiegati imp) {
int n=0;
register int i;
if (num_impiegati<100) {
do {
printf("Numero Impiegati presenti in archivio: %d\n",num_impiegati);
printf("Inserisci il numero di impiegati (>0 <=100): \n");
scanf("%d",&n);
} while ((n<1) || (n+num_impiegati>DIM_MAX));
printf("Inserisci gli Impiegati (premere INVIO)\n");
for (i=num_impiegati; i<num_impiegati+n; i++) {
printf("Inserire matricola dell'impiegato %d: ",i+1);
scanf("%d",&imp[i].matricola);
printf("Inserire nome dell'impiegato %d: ",i+1);
scanf("%s",imp[i].Nome);
printf("Inserire cognome dell'impiegato %d: ",i+1);
scanf("%s",imp[i].Cognome);
printf("Inserire dipartimento dell'impiegato %d: \n",i+1);
scanf("%s",imp[i].Dipartimento);
}
num_impiegati=n+num_impiegati;
} else printf("\nArchivio pieno!!!\n");
}
//stampa a video il contenuto dellarchivio
void visualizza_archivio(const Impiegati imp) {
register int i;
for (i=0; i<num_impiegati; i++) {
printf("Matricola dell'impiegato %d: %d\n",i+1, imp[i].matricola);
246 Capitolo sesto

printf("Nome dell'impiegato %d: %s\n",i+1,imp[i].Nome);


printf("Cognome dell'impiegato %d: %s\n",i+1,imp[i].Cognome);
printf("Dipartimento dell'impiegato %d: %s\n",i+1,imp[i].Dipartimento);
}
}

//ricerca se un impiegato presente in archivio


void ricerca_impiegato(Impiegati imp, Stringa cogn){
register int i,j;
int trovato=0;
for (i=0; i<num_impiegati; i++) {
if (strcmp(cogn,imp[i].Cognome)==0) {
trovato=1;
printf("Nome dell'impiegato %d: %s\n",i+1,imp[i].Nome);
printf("Cognome dell'impiegato %d: %s\n",i+1,imp[i].Cognome);
printf("Dipartimento dell'impiegato %d: %s\n",i+1,imp[i].Dipartimento);
}
}
if (!trovato)
printf("\n Impiegato non presente");
}

//modifica il dipartimento di un impiegato


void modifica_impiegato(Impiegati imp, Stringa cogn){
register int i,j;
int trovato=0;
i=0;
while (!trovato && i<num_impiegati) {
if (strcmp(cogn,imp[i].Cognome)==0)
trovato=true;
i++;
}
if (!trovato)
printf("\n Impiegato non presente");
else {
printf("Inserire nuovo Dipartimento dell'impiegato %d: \n",i);
scanf("%s",imp[i-1].Dipartimento);
}
}
// carica archivio da file
void carica_archivio (Impiegati imp, Stringa f) {
FILE * fp;
int ret;
register int i,j;
if (!(fp=fopen(f,"r"))) {
printf("\n L'archivio non puo' essere caricato\n");
}
else {
ret=fscanf(fp,"%d",&num_impiegati);
if (ret!=EOF) {
printf("\nNumero di impiegati presenti in archivio: %d\n",num_impiegati);
if (num_impiegati>0) {
for (i=0; i<num_impiegati; i++) {
fscanf(fp,"%d",&imp[i].matricola);
fscanf(fp,"%s",imp[i].Nome);
fscanf(fp,"%s",imp[i].Cognome);
Il linguaggio C 247

fscanf(fp,"%s",imp[i].Dipartimento);
}
}
}
else {
printf("\nArchivio vuoto!!!\n");
num_impiegati=0;
}
}
}
// salva archivio su file
void salva_archivio(const Impiegati imp, Stringa f) {
register int i,j;
FILE*fp;
if (!(fp=fopen(f,"w"))) {
printf("\n L'archivio non puo' essere salvato\n");
}
else {
fprintf(fp,"%d\n",num_impiegati);
for (i=0; i<num_impiegati; i++) {
fprintf(fp,"%d\n",imp[i].matricola);
fprintf(fp,"%s\n",imp[i].Nome);
fprintf(fp,"%s\n",imp[i].Cognome);
fprintf(fp,"%s\n",imp[i].Dipartimento);
}
}
}
// main
int main () {
Impiegati i;
char opzione;
Stringa cogn, file;
while (opzione!='7') {
printf ("\n-PROGRAMMA DI GESTIONE DI UN ARCHIVIO DI IMPIEGATI-\n");
printf ("[1] Inserisci Impiegati\n");
printf ("[2] Ricerca Impiegato\n");
printf ("[3] Modifica Impiegato\n");
printf ("[4] Visualizza Archivio\n");
printf ("[5] Carica Archivio\n");
printf ("[6] Salva Archivio\n");
printf ("[7] Esci\n");
scanf("%s",&opzione);
switch (opzione) {
case '1':
inserisci_impiegato(i);
break;
case '2':
printf("\nInserisci il cognome dell'impiegato che vuoi ricercare: ");
scanf("%s",cogn);
ricerca_impiegato(i,cogn);
break;
case '3':
printf("\nInserisci il cognome dell'impiegato che vuoi ricercare: ");
scanf("%s",cogn);
modifica_impiegato(i,cogn);
break;
248 Capitolo sesto

case '4':
visualizza_archivio(i);
break;
case '5':
printf("\nInserisci il nome del file-archivio: ");
scanf("%s",file);
carica_archivio(i,file);
break;
case '6':
printf("\nInserisci il nome del file su cui salvare l'archivio: ");
scanf("%s",file);
salva_archivio(i,file);
break;
case '7':
printf("Uscita programma...\n");
system("PAUSE");
return 0;
default:
printf("Opzione non supportata\n");
}
}
return 1;
}
Capitolo settimo

Il linguaggio dellambiente MATLAB

7.1. Caratteristiche del linguaggio


MATLAB un vero e proprio ambiente di progetto rivolto principalmente allo
sviluppo di programmi per lanalisi numerica. Tale ambiente utilizza un linguaggio
di programmazione con una sintassi simile a quella del C, e, cos come i linguaggi
di programmazione pi comuni, esso mette a disposizione del programmatore tutti
i pi noti tipi elementari di dato:
- dati di natura numerico:
o tipo intero,
o tipo reale,
- dati di natura alfanumerica:
o caratteri,
o stringhe.
In particolare linstanziazione di una variabile numerica avviene attraverso la
sintassi:
var=val
dove var e val rappresentano rispettivamente il nome ed il valore della variabile
numerica. Mentre linstanziazione di una variabile alfanumerica avviene attraverso
la sintassi:
var=val
dove var e val rappresentano rispettivamente il nome ed il valore della variabile
alfanumerica. Una variabile alfanumerica contraddistinta dalla presenza degli
apici, allinizio e alla fine del valore della variabile stessa.
Altra caratteristica fondamentale di MATLAB il concetto di funzione. Ogni
programma, sottoprogramma, procedura o funzione del linguaggio sempre
trattata come una funzione costituita o meno da un insieme di parametri di
ingresso-uscita. Ogni dichiarazione di funzione MATLAB preceduta dalla parola
chiave function.
Per quanto riguarda le istruzioni, il linguaggio MATLAB fornisce sia
enunciati o statement semplici che di controllo.
Quelli semplici sono:
- lassegnazione, che fornisce ad una variabile il valore che si ottiene
calcolando il risultato di unespressione composta in generale di costanti,
variabili, operatori e funzioni; essa si indica (se var la variabile che
riceve il valore ed E lespressione che lo fornisce) con un sintassi del
tipo: var=E
250 Capitolo settimo

- lattivazione di funzioni, eventualmente con il passaggio dei dati da


elaborare e con la ricezione di uno pi risultati. Si noti che MATLAB,
come del resto altri linguaggi, mette a disposizione un insieme molto
ampio di funzioni predefinite quali ad esempio quelle per il calcolo
numerico oppure quelle per la lettura e la scrittura di dati sui supporti di
ingresso e uscita.
Gli enunciati di controllo si dividono al loro volta in:
- enunciati composti, che si ottengono disponendo in sequenza enunciati
semplici, di selezione e di iterazione;
- enunciati di selezione;
- enunciati di iterazione.
Come anticipato il linguaggio MATLAB prevede la costruzione di programmi
con la struttura di funzioni composte da un unico blocco di istruzioni, costituente la
sezione esecutiva. A differenza di altri linguaggi, manca nel corpo di una funzione
MATLAB la sezione dichiarativa in quanto tutte le variabili, come vedremo, sono
automaticamente trattate e riconosciute o come set di reali o di caratteri. Una
funzione pu richiamare o essere richiamata da altre funzioni e pu essere o meno
caratterizzata dalla presenza di parametri di ingresso e di uscita.
In figura 1 riportato un esempio di programma MATLAB che richiama due
funzioni. La funzione A un esempio di funzione avente solo parametri di
ingresso, mentre la funzione B un esempio di funzione avente parametri sia di
ingresso che di uscita. Il programma principale esso stesso una funzione senza
parametri. Tutte le variabili usate allinterno di una funzione MATLAB sono ad
essa locali, ossia hanno una visibilit limitata alla sola funzione in cui sono usate.
In altri termini una variabile definita allinterno della funzione A non pu essere
usata nella funzione B e viceversa.
function nome_programma
enunciato 1 del programma
.
funzioneA (lista_parametri_ingresso)
.
[lista_paramteri_ucita] = funzioneB(lista_parametri_ingresso)
.
enunciato N del programma

function funzioneA(lista_parametri_ingresso)
enunciato 1 della funzioneA
..
enunciato N1 della funzioneA

function [lista_paramteri_ucita] = funzioneB(lista_parametri_ingresso)


enunciato 1 della funzioneB
..
enunciato N2 della funzioneB
Figura 1 Esempio di programma Matlab

7.1.1. Il vocabolario del lingua