Alla Scoperta Dei Fondamenti Dell'Informatica
December 21, 2022 | Author: Anonymous | Category: N/A
Short Description
Download Alla Scoperta Dei Fondamenti Dell'Informatica...
Description
u o r
d
g
i t o r
g
e
i i t o r
u o r
d
e
e
i
e
A. Chianese, V. Moscato, A. Picariello ALLA SCOPERTA DEI FONDAMENTI DELL’INFORMATICA
l i
l i
Questo testo è uno strumento didattico utile per la comprensione dei principali metodi ed algoritmi di Analisi Numerica e delle problematiche connesse all’uso di un elaboratore nella risoluzione di un problema di matematica applicata. Gli argomenti trattati riguardano: l’analisi degli errori, la risoluzione di sistemi lineari e di equazioni non lineari, l’interpolazione e lo smoothing di dati, l’approssimazione 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 dell’Università di Napoli.
COD. U
ANGELO CHIANESE VINCENZO MOSCATO ANTONIO PICARIELLO
Alla scoperta dei fondamenti dell’informatica Un viaggio nel mondo dei BIT
35,00
u o r
d
g
i t o r
l i
g
e
i i t o r
u o r
d
e
e
i
e
l i
MANUALI
Angelo Chianese, Vincenzo Moscato, Antonio Picariello
Alla scoperta dei fondamenti dellíinformatica Un viaggio nel mondo dei BIT
Liguori Editore
Questa opera è protetta dalla Legge sul diritto d’autore (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, all’uso 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 all’autorizzazione scritta dell’Editore. La violazione delle norme comporta le sanzioni previste dalla legge. Il regolamento per l’uso 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 L’utilizzo 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 dell’informatica. 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.
L’INFORMAZIONE E LE SUE RAPPRESENTAZIONI ....1
1.1. L’informatica ed il mondo moderno.......................................................1 1.1.1. Una definizione di Informatica..............................................................2 1.1.2. La rappresentazione dell’informazione .................................................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. L’istruzione 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. L’astrazione 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. 6.1.
IL LINGUAGGIO C.......................................................185 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. L’intestazione 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 dell’algoritmo 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 dell’I/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 DELL’AMBIENTE 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 dell’algoritmo................................................................261 7.2.4. Assegnazione.....................................................................................261 7.2.5. Richiamo di funzione ........................................................................262 Funzioni predefinite ....................................................................................263 Funzioni per l’I/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. L’inserimento in un vettore ..............................................................272 7.3.3. L’eliminazione in un vettore .............................................................273 7.3.4. L’eliminazione di una colonna da una matrice..................................275 7.3.5. L’eliminazione 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. 8.1.
LA TRADUZIONE DEI PROGRAMMI..........................299 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. L’ambiente DEV-C++.......................................................................313 8.4.2. L’ambiente MATLAB.......................................................................319
CAPITOLO NONO 9. 9.1.
LA PROGRAMMAZIONE ORIENTATA AGLI OGGETTI 331 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 dell’ereditarietà .........................................................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. L’evoluzione 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. L’architettura 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. L’interprete 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 dell’informazione................................................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 all’Internetworking.............................................................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 dell’informazione 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. L’e-commerce...............................................................................410 12.3.2. L’e-banking ..................................................................................410 12.3.3. L’e-trading....................................................................................411 12.3.4. L’e-procurement ...........................................................................411 12.3.5. Internet e la Pubblica Amministrazione: l’e-governement ...........412 12.3.6. Internet ed il mondo della formazione: l’e-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: l’evoluzione 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. L’evoluzione 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 dell’informatica” 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 dell’informatica. Negli ultimi anni il mondo dell’informatica 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 dell’uomo 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 dell’informatica, 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 l’intenzione di appassionare. Abbiamo così scoperto che un viaggio nella storia dell’informatica 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, all’affermazione 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 l’Ingegneria 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 dell’editoria. 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 dell’esperienza 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 l’informatica. 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 dell’informatica 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 dell’informatica: 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 d’Acierno e Antonio Penta. Non abbiamo bisogno di ringraziare l’amico 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 nell’Università 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
L’informazione e le sue rappresentazioni
1.1.
L’informatica ed il mondo moderno
L’attuale società moderna è sempre più dipendente dall’informatica. In molti aspetti della vita quotidiana è possibile avvertire la “presenza” di elaboratori elettronici, il più delle volte nascosti all’interno 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 dall’avvento di Internet che ha abbattuto ogni tipo di barriera e distanza sociale, culturale e tecnologica, rendendo possibile a tutti l’accesso a informazioni e servizi distribuiti in diverse parti del globo terrestre. I moderni elaboratori, attraverso l’insieme delle applicazioni software (sistema operativo più programmi) in esso contenute, mettono a disposizione dell’utente 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, l’utilizzo 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 l’utilizzo 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 all’uso 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 l’acquisto di merci e servizi (e-commerce); nel settore televisivo e dello spettacolo, è grazie all’informatica, che si stanno diffondendo sempre nuovi e più sofisticati servizi interattivi; parte del settore dell’intrattenimento (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 dell’umanità (viaggi spaziali, scoperte nel campo medico e scientifico, etc.) sono state in parte rese possibili grazie all’informatica.
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 l’utilizzo delle macchine informatiche. In realtà l’informatica è un campo di studio molto giovane. I primi elaboratori risalgono infatti solo al 1946, ed allora, essa non era considerata una disciplina separata dall’elettronica e dalla matematica. Nel corso degli anni, con l’incremento 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. L’informatica, ovvero la scienza della risoluzione dei problemi con l’aiuto degli elaboratori, divenne quindi una disciplina autonoma. L’informatica è una disciplina complessa che abbraccia campi molto diversi tra loro, in cui l’elaboratore 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 l’informatica è 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 l’infrastruttura fondamentale per lo sviluppo culturale ed economico della società. In tale ottica l’elaboratore assolve al compito di gestire e memorizzare le informazioni o dati, mentre l’esperto informatico progetta le applicazioni necessarie ad organizzare e quindi gestire le informazioni sia mediante l’uso 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 dell’informazione
Affinché un’informazione 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
L’informazione 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 l’informazione 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 un’informazione hanno bisogno che essa sia appropriatamente “rappresentata”. A tale proposito, la storia dell’uomo è ricca di esempi che testimoniano l’importanza della rappresentazione efficace delle informazioni: basti pensare che se non fosse esistita la scrittura non avremmo un resoconto oggettivo degli avvenimenti dell’uomo 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 dell’universo rappresentato. Una tale rappresentazione è un’approssimazione 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 dall’uso della rappresentazione discreta in quanto in molte applicazioni reali non sempre hanno importanza tutti gli infiniti valori che l’informazione reale può assumere. È noto, ad esempio, a tutti che in una proiezione cinematografica l’occhio 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 dell’utilizzatore 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 un’informazione per essere correttamente elaborata deve essere codificata in una rappresentazione comprensibile all’elaboratore stesso, e, a tale proposito, risultano fondamentali i concetti di “codifica” e “codice” riportati di seguito. La codifica è l’insieme di convenzioni e di regole da adottare per trasformare un’informazione 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 dell’informazione ed è definito dai seguenti elementi: - i simboli che sono gli elementi atomici della rappresentazione; - l’alfabeto che rappresenta l’insieme dei simboli possibili: con cardinalità (n) del codice si indica il numero di elementi dell’alfabeto; - le parole codice o stringhe che rappresentano sequenze possibili (ammissibili) di simboli: per lunghezza (l) delle stringhe si intende poi il numero di simboli dell’alfabeto da cui ciascuna parola codice risulta composta; - il linguaggio che definisce le regole per costruire parole codici che abbiano significato per l’utilizzatore del codice. La scrittura è l’esempio più noto di codifica nella quale trovare facilmente un esempio dei concetti indicati. Siano allora V = {v1, v2, ..., vm } l’insieme 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 dell’alfabeto; - 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 l’alfabeto A = {-,.} del codice Morse con n=2, si hanno le diverse rappresentazioni riportate in tabella 1. nl 2 4
8
16
l=1 .
l=2 --. ...
l=3 ----. -.-.. .-.-. ..…
l=4 ------. --.--.. -.--.-. -..-… .--.--. .-..-.. ..-..-. ....…
Tabella 1 – Esempi di codici Morse a lunghezza differente
Se la codifica deve mettere in corrispondenza biunivoca i valori dell’informazione 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
L’informazione 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 l’esempio nella tabella 2 mostra nei primi due casi. Informazione Giorni settimana Colori semaforo Risposta
Suoi Valori lunedì, martedì, mercoledì, giovedì, venerdì, sabato, domenica rosso, giallo, verde si, no
Sue rappresentazioni --- , --. , -.- , -.. , .-- , .-. , ..-- , -., .-,.
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 dell’informatica nella società ha comportato un’estensione 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 l’adozione 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 all’interno dell’elaboratore si basa sull’alfabeto 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 l’assenza di un buco su un supporto ottico come quello dei cd-rom; l’apertura 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 dall’ambiente 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 dell’uno, 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 dell’informazione 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, l’aritmetica 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
L’informazione 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 B KB MB GB TB
Nome Byte KiloByte MegaByte GigaByte TeraByte
Numero byte 1 210=1024 220=1.048.576 230=1.073.741.824 240=1.099.511.627.776
Numero bit 8 8.192 8.388.608 8.589.934.592 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 dell’informazione, allora si individua il numero b di byte tale che: 2 (b*8) > K In altri termini, all’interno 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 1 2 3 4
Numero di bit (b*8) 8 16 24 32
2 (b*8) 28 216 224 232
Configurazioni 256 65.536 16.777.216 4.294.967.296
Tabella 4 – Valori di informazione rappresentabili al variare del numero di byte
L’adozione 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 all’interno 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 nell’intervallo 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 all’insieme dei valori rappresentabili. Si dice condizione di underflow quella che si verifica quando il risultato dell’operazione è minore del più piccolo valore rappresentabile (min). Si chiama overflow la condizione opposta, ossia quella che si verifica quando il risultato dell’operazione è maggiore del più grande valore rappresentabile (max). Infine il risultato dell’operazione non appartiene all’insieme quando non è compreso nell’insieme 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 all’insieme di definizione per una calcolatrice decimale dotata di sole tre cifre, con intervallo di definizione formato da numeri interi compresi nell’intervallo [-999,+999]. Operazione 200 +100 730 + 510 -500-720 2:3
Condizione risultato rappresentabile Overflow Underflow risultato non rappresentabile
Tabella 5 – Esempi di operazioni che generano risultati rappresentabili e non
Anche l’algebra 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 all’ordine con cui le operazioni vengono eseguite, come i casi seguenti mostrano usando la stessa calcolatrice di tre cifre. a 100
b 900
C 600
a 200
b 90
C 88
a + (b - c) 100 + (900 - 600) a × (b - c) 200 × (90 – 88)
condizione ok
(a + b) – c (100 + 900) - 600
Condizione Overflow
condizione Ok
a×b–a×c 200 × 90 – 200 × 88
Condizione Overflow
Tabella 6 – Applicazione delle proprietà associativa e distributiva per algebre a precisione finita
L’algebra 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 all’intervallo di definizione vengono ricondotti ad esso prendendo il resto della divisione dei valori per il periodo. intervallo [0,360] [0,60] [0,60]
periodo 360 60 60
valore 1200 61 55
divisione 1200 : 360 61 : 60 60 : 55
resto 120 1 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 nell’intervallo fissato. Per comodità di disegno si è ristretto l’intervallo 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
L’informazione 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 all’interno dei calcolatori viene adottata un’algebra 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 Peso 7
0 0 1 0 Peso 6
1 0 1 0 Peso 5
0 0 1 0 Peso 4
0 0 1 0 Peso 3
1 0 1 0 Peso 2
0 0 1 0 Peso 1
1 1 1 0 Peso 0
165 1 255 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 un’importanza variabile a seconda della relativa posizione (nel sistema decimale la prima cifra a destra indica l’unità, 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 10 2 8 16
Denominazione Decimale Binaria Ottale Esadecimale
Valori delle cifre 0123456789 01 01234567 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
L’informazione 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 all’altra 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 un’altra 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: Valore
l 1
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 l’esempio 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 0 1 2 3 4 5 6 7
Binario 000 001 010 011 100 101 110 111
Esadecimale 0 1 2 3 4 5 6 7 8 9 A B C D E F
Binario 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 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 + ……
L’informazione 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. L’insieme 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 dell’ultima il bit più significativo come l’esempio 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 l’algoritmo nell’esempio 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 nell’ordine 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 l’algoritmo 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.
L’informazione e le sue rappresentazioni
15
L’adozione 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 dell’addizione 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 nell’aritmetica decimale) mentre nella sottrazione, in presenza dell’operazione “0 – 1”, si deve attivare il prestito dalle cifre più a sinistra (come nell’aritmetica decimale).
Esempio 3 – Esempi di somma, sottrazione e prodotto fra numeri binari
La rappresentazione finora presentata, che fa uso dell’equazione (1) per la codifica da binario a decimale e dell’algoritmo nell’esempio 1 per la codifica da decimale a binario, viene anche detta “in binario puro”. Essa rende possibile rappresentare tutti i numeri interi positivi appartenenti all’intervallo [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, nell’evoluzione dell’aritmetica 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 all’interno 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 all’intervallo: [-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 all’intervallo [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 nell’attraversamento 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 nell’intervallo [0, 2l - 1- 1], rappresentano se stesse (numeri positivi), mentre le configurazioni col bit più significativo uguale a uno, cioè quelle rientranti nell’intervallo [2l - 1,2l - 1], rappresentano i numeri negativi che si ottengono traslando a sinistra l’intervallo di 2l, cioè l’intervallo [-2l - 1,-1].
Figura 5: Rappresentazione per complemento a 2
L’informazione e le sue rappresentazioni
17
Nella rappresentazione per complemento a 2, i valori rappresentati sono compresi nell’intervallo: [-2l -1, 2l -1 - 1] sono sempre 2 l : -
[0,2l -1] per i valori positivi [-2l - 1,-1] per i valori negativi.
l’intervallo 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 nell’intervallo [0, 281], cioè [0, 255], oppure i numeri relativi nell’intervallo [-27, 27-1], cioè [-128, 127]. Con 16 bit (2 byte) si rappresentano i numeri naturali nell’intervallo [0,216-1], cioè [0,65535], oppure i numeri relativi nell’intervallo [-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 l’esempio 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 l’uno e dell’uno 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. Un’altra 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 l’esempio 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 l’uno) 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 l’intervallo 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].
L’informazione 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 nell’intervallo [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 l’esempio 6 dimostra. È così possibile costruire dei circuiti che fanno solo addizioni.
Esempio 6 – Operazioni in complementi a 2
20
1.2.3.
Capitolo primo
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 l’ampiezza 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 dall’esponente 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
L’informazione 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 0,00347 0,000348 0,00987 0,000987
ARROTONDAMENTO 0,0035 0,0003 0,0099 0,0010
ERRORE 3*10-5 = 0.3 *10-4 48*10-6 = 0.48*10-4 3*10-5 = 0.3 *10-4 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 10 2
m
L’insieme R è anche costituito da infiniti valori (è definito dall’intervallo ], [). I numeri reali rappresentabili sono invece definiti in un insieme limitato con estremi predefiniti [-minreal, maxreal]. Si definiscono: - l’overflow come la condizione che si verifica quando i valori o sono più piccoli di minreal o più grandi di maxreal; - l’underflow 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 un’altra 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 0,384 1345 64350 333 0,0048 0,0000001
Valore 3,8400 10-1 1,3450 103 6,4350 104 3,3300 102 4,8000 10-3 1,0000 10-8
Tabella 14 – Rappresentazioni Normalizzate
e la condizione di overflow quando: x >9,9999
1099
x < 1,0000
10-99
e di underflow quando:
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
L’informazione 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 l’esponente.
Figura 8 – Formati a singola e doppia precisione
Nella stringa di bit, si succedono nell’ordine 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 l’assenza sia del primo bit che del bit della virgola perché sono sempre presenti: l’insieme dell’uno implicito, della virgola implicita e delle cifre esplicite prende il nome di significante. Argomento Bit del segno Bit per l’esponente Bit per la mantissa Cifre decimali mantissa Esponente (rappresentazione) Esponente (valori)
Precisione singola 32 Bit 1 8 23 Circa 7 (23/3.3) Base 2 ad eccesso 127 [-126, 127]
Precisione doppia 64 bit 1 11 52 Circa 15 (52/3.3) base 2 ad eccesso 1023 [-1022, 1023]
Tabella 15 – Caratteristiche formati singola e doppia precisione
L’informazione e le sue rappresentazioni
25
L’esempio di seguito riepiloga alcuni dei procedimenti illustrati per la rappresentazione di numeri da binario a decimale e viceversa. Problema Si vuole convertire il numero 16 in binario utilizzando una rappresentazione binaria pura su 8 bit.
Si vuole convertire il numero -12 in binario utilizzando una rappresentazione per complementi a 2 su 5 bit
Si vuole convertire il numero -10 in binario utilizzando una rappresentazione per complementi a 2 su 4 bit.
Soluzione Poiché la rappresentazione binaria pura consente la codifica di numeri interi postivi allora 16 è codificabile attraverso questa rappresentazione. Utilizzando 8 bit l’intervallo di rappresentazione è l’insieme dei numeri interi postivi compresi in [0,281], ovvero in [0,255]. Poiché 16 è racchiuso in tale intervallo è possibile passare alla sua codifica. Utilizzando l’algoritmo dell’esempio (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. Poiché la rappresentazione per complementi a 2 consente la codifica di numeri relativi allora -12 è codificabile attraverso questa rappresentazione. Utilizzando 5 bit l’intervallo di rappresentazione è l’insieme 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 l’algoritmo dell’esempio (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 Poiché la rappresentazione per complementi a 2 consente la codifica di numeri relativi allora -10 è codificabile attraverso questa rappresentazione.. Utilizzando 4 bit l’intervallo di rappresentazione è l’insieme dei numeri interi compresi in [-24-1,24-1-1],
26
Capitolo primo
Si vuole convertire il numero 111000 da complementi a 2 in decimale.
Si vuole codificare il numero reale 8.25 utilizzando la rappresentazione in virgola mobile nel formato a singola precisione.
ovvero in [-8,7]. Poiché -10 non è racchiuso in tale intervallo la sua codifica non è possibile. Innanzitutto si esamina il primo bit. Poiché esso è pari ad 1, il numero è negativo e quindi, per determinarne il modulo, si complementano ad 1 tutti i 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. Innanzitutto si codificano in binario la parte intera e frazionaria della mantissa e l’esponente (utilizzando la rappresentazione per eccessi a 2l-1) ottenendo 0 111.01 * 200000000. Normalizzando la mantissa, si ottiene: 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; l’AND è 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; l’OR è 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à.
L’informazione 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 l’informazione in modo digitale. Lo sviluppo della tecnologia dei sistemi di comunicazione e di gestione delle informazioni digitali nei campi dell’editoria, 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 l’integrazione 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 l’informazione 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 dall’analisi 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 dell’uomo: 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 l’integrazione 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 dell’informazione analogica in digitale avviene con due processi distinti e disposti l’uno dopo l’altro:
L’informazione 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 l’informazione rappresentata sia utilizzabile. Ad esempio non ha molta importanza nel caso delle comunicazioni telefoniche, selezionare un numero di campioni molto elevato nell’unità di tempo della voce, perché l’orecchio 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 è l’inverso dell’intervallo di tempo che intercorre tra l’acquisizione 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 dell’appartenenza a dati intervalli di valori, detti intervalli di quantizzazione.
Figura 10 – Quantizzazione di un segnale
Sia campionamento che quantizzazione introducono un’approssimazione 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, nell’altro 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;
L’informazione e le sue rappresentazioni
31
-
la compressione con perdita di informazione (lossy, irreversibile) che invece sfrutta le ridondanze nella percezione dell’informazione. La compressione lossless avviene tramite una classe di algoritmi che consentono di ricostruire tutta l’informazione iniziale partendo da quella compressa. Non sempre si ottengono riduzioni significative. I metodi lossy comportano riduzioni notevoli delle dimensioni, ma la ricostruzione dell’informazione 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 dell’occhio umano di essere poco sensibile a lievi cambiamenti di colore in punti contigui, e quindi eliminano questi lievi cambiamenti appiattendo il colore dell’immagine. 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 l’audio che si basa alle proprietà psicoacustiche dell’udito 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 dell’alfabeto 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 l’inizio 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-885915.
L’informazione 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 all’altro seguendo i collegamenti. L’idea formalizzata di ipertesto nasce nella metà del secolo scorso, la maturazione tecnologica avviene solo negli anni ’80. L’approccio ipertestuale è l’evoluzione 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 l’altra, 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 si apre una indicazione di formato e con la si chiude. Ad esempio: che bello prescrive la visualizzazione di “che bello” in rosso, mentre: HTML consente di scrivere HTML con caratteri di dimensioni più grandi. Il DOC è invece il formato più diffuso dei documenti formattati a causa dell’ampia diffusione dell’editor 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.
L’informazione e le sue rappresentazioni
1.4.2.
35
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 l’immagine ad una matrice di punti detti pixel (da picture element) mentre la terza limita l’insieme di colori che ogni pixel può assumere ad un sottoinsieme definito. L’immagine digitale è quindi una matrice bidimensionale di numeri, ciascuno dei quali è la misura di una proprietà fisica (colore) di un’area 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 l’asse 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 dell’immagine reale. Più la dimensione dei pixel è piccola, minore è l’approssimazione 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 un’immagine (larghezza e altezza) sono misurate in pollici. La dimensione dell’immagine 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 l’immagine. Maggiore è la risoluzione dell’immagine, 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 l’immagine viene digitalizzata: ad esempio la risoluzione di 600 dpi indica che ciascun pixel misura 1/600 di pollice. Ad ogni pixel dell’immagine 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à dell’immagine. La profondità di colore è la misura della capacità di rappresentare o distinguere varie sfumature di colore. Un’immagine rappresentata con una profondità di colore di 6 bit, riesce a distinguere tra 64 livelli di colore, e all’aumentare del numero di bit, aumenta il livello di dettaglio. Se l’immagine è 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 l’alta 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.
L’informazione 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 all’arancio, 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 dell’inchiostro di assorbire la luce. Quando la luce bianca colpisce gli inchiostri, alcune lunghezze d’onda 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 l’inchiostro 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 sull’ipotesi che difficilmente in una immagine sono presenti contemporaneamente 16 milioni di colori diversi. Consiste nell’utilizzare una tabella numerica in cui sono codificati solo i colori effettivamente presenti nell’immagine: 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 dell’immagine 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 dell’utilizzo dell’immagine (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 l’individuazione degli elementi riprodotti. Nelle immagini bitmap è quindi il pixel con le sue caratteristiche l’elemento di riferimento. Il singolo pixel da solo detiene un contenuto informativo limitato, se viene invece considerato in combinazione all’insieme di pixel adiacenti si possono estrarre caratteristiche più significative. Elaborare un’immagine significa modificarne il contenuto allo scopo di evidenziare alcune caratteristiche. Analizzare un’immagine 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:
L’informazione 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à dell’immagine 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à dell’immagine 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 sull’uso 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 l’animazione. 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 l’applicazione 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 l’immagine 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 l’informazione necessaria a riprodurre l’immagine, 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 dell’immagine 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 dell’occhio umano ha la caratteristica di mantenere impressa un’immagine per alcuni millisecondi prima che svanisca. Se si proietta una successione di immagini a determinate velocità, l’occhio 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 l’altra. 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.
L’informazione e le sue rappresentazioni
41
Per digitalizzare l’immagine 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 all’esempio 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. L’organizzazione 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
1.4.4.
Capitolo primo
La codifica del suono
Il suono è un segnale analogico funzione del tempo consistente in vibrazioni che formano un’onda, la cui ampiezza misura l’altezza dell’onda 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 l’orecchio 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 Telefono Parlato Radio mono Radio stereo Audio Cassetta Compact Disk
Frequenza di campionamento (Hz) 8.000 11.025 22.050 22.050 44.100 48.000
Profondità (bit)
Mono/stereo
Dimensione per un minuto
8 8 16 16 16 16
mono mono mono stereo stereo stereo
469,00 Kb 646,00 Kb 2,52 Mb 5,05 Mb 10,10 Mb 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.
L’informazione e le sue rappresentazioni
1.5.
43
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. L’informazione è 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 l’informazione 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 l’uomo tratta informazioni l’elaboratore 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. L’ambiguità può essere risolta dando al dato una interpretazione. L’informazione non è altro che la percezione del dato attraverso un processo di interpretazione. In altre parole l’informazione cattura il significato del dato. Quando l’elaborazione 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 l’uso. 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 l’attributo 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 anch’essi 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 è l’informazione 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. L’eXensible 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
L’informatica 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. L’algoritmo è 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 l’elenco 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 dell’esecutore.
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 l’esecuzione 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 l’unità 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 dell’affidabilità 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à. L’architettura 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 d’inchiostro 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 l’indirizzo del registro interessato dall’operazione; 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 un’area 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
Un’altra 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 nell’ottica 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 all’attivazione 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 all’istruzione, e infine di eseguire l’istruzione. 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 l’esecuzione di una istruzione può avere inizio solo se la precedente è stata portata a termine. Perché l’intero 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 l’algoritmo detto ciclo del processore fino allo spegnimento del sistema. Le tre fasi del ciclo vengono anche dette fase fetch, operand assembly ed execute. L’unità logico aritmetica esegue operazioni aritmetiche, di confronto o bitwise sui dati della memoria centrale o dei registri interni. L’esito dei suoi calcoli viene segnalato da appositi bit in un registro chiamato Condition Code. A seconda dei processori l’ALU può essere molto complessa. Nei sistemi attuali l’ALU 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 dell’architettura 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 un’intera 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 un’operazione uno degli operandi, e al termine della stessa operazione il risultato calcolato. In questo caso i registri Op1 e Op2 diventano interni all’ALU. 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 l’altra 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. L’address 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 sull’address bus il proprio indirizzo. In altri termini attraverso l’address 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 è l’unico elemento che fornisce l’indirizzo all’address bus; - memorie e dispositivi di input ed output devono ascoltare l’address bus per attivarsi quando su di esso compare il proprio indirizzo identificativo; nel caso della memoria l’attivazione avviene quando viene riconosciuto l’indirizzo 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 l’altro. 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. L’address e il data bus sono paralleli e le loro dimensioni caratterizzano i sistemi di calcolo. Il numero di bit dell’address 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, un’onda 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 l’esecuzione 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 nell’unità di tempo dalla CU. Ad esempio, se si assume che ad ogni ciclo di clock corrisponde esattamente l’esecuzione di una sola operazione, allora la frequenza del clock indica il numero di operazioni che vengono eseguite nell’unità 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 l’esecuzione 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 l’importanza 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 nell’ordine: - con il primo battito di clock ponendo l’indirizzo del registro di memoria di cui vuole leggere il contenuto sull’address 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 l’indirizzo del registro di memoria di cui vuole leggere il contenuto sull’address 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. Nell’esempio si è fatta l’ipotesi che la memoria sia in grado di gestire sia l’inserimento 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à. L’insieme 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 dall’ALU; - 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. L’esecuzione di una istruzione da parte della CU consiste nell’inoltro 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 l’altra le microistruzioni che un decodificatore trasforma in segnali di abilitazione. L’istruzione 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. L’insieme dei microprogrammi composti dalle microistruzioni memorizzate nella memoria interna alla CU prende il nome di firmware. L’esecuzione 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 l’elaborazione di documenti, la ricerca di informazioni in Internet, il foglio elettronico, la gestione della segreteria studenti di una università, il sistema operativo a finestre. L’insieme 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 dall’hardware: 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 dell’esecuzione del programma il programmatore o l’operatore 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 un’altra è svolto in automatico mediante l’interpretazione di comandi che l’utente 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 all’avvio del sistema, quando termina un’applicazione 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 l’applicazione utente. Il middleware è il software che fornisce un’astrazione di programmazione che maschera l’eterogeneità 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
2.4.
61
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 dell’output. 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 nell’architettura 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 anch’essa 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 l’impressione all’utente 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 dell’istruzione successiva; in caso contrario comincia l’esecuzione 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. Nell’attesa 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 all’interno 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 quest’ultima. 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 l’elevato 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 l’illusione 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. L’insieme di istruzioni (repertorio) che si possono descrivere comprendono operazioni che: - spostano stringhe di bit da un registro all’altro di memoria; - attivano l’ALU per effettuare la somma aritmetica, l’AND, l’OR tra coppie di stringhe di bit, o la negazione (NOT) del contenuto dell’accumulatore; - eseguono lo scorrimento o la rotazione a destra o a sinistra dei bit contenuti nell’accumulatore; - provvedono ad interrogare i bit del registro di condizione (CC) per determinare come procedere nell’esecuzione 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 l’allocazione in memoria di dati e programmi può essere statica o dinamica. Nel primo caso l’allocazione avviene prima dell’inizio dell’esecuzione 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 nell’area riservata alle istruzioni cambiando il contenuto dei registri di memoria e cambiando di fatto la struttura del programma. L’allocazione in memoria comporta un’associazione 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 l’indirizzo di memoria occupato. L’indicazione 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 l’operazione da compiere; l’insieme dei Cop prendere il nome di repertorio di istruzioni e dipende dalla specifica CPU; - Pdi sono i puntatori ai dati che servono per svolgere l’operazione Cop detti anche di input; si noti che esistono istruzioni che non hanno operandi di input; - Pdo sono i puntatori ai dati prodotti dall’operazione Cop detti anche di output; si noti che esistono istruzioni che non hanno operandi di output; - Pis è il puntatore all’istruzione da svolgere al termine dell’esecuzione 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 l’altra. 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 dell’istruzione 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 dell’istruzione 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 dell’istruzione da eseguire; - conoscere i puntatori ai dati di input ed output; - ricevere il puntatore all’istruzione da eseguire successivamente. La fase fetch si conclude con l’aggiornamento del registro PI con il valore del puntatore all’istruzione 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 dell’esecuzione di una istruzione la CU riprende il suo cammino partendo dal contenuto del registro PI. Le istruzioni del programma vengono eseguite una dopo l’altra 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 l’indirizzo 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 un’altra da eseguire successivamente. Questo spiega perchè quando termina un’applicazione un elaboratore torna ad eseguire i programmi del sistema operativo. Dai programmi del sistema operativo si passa ad un’altra applicazione in un’alternanza 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 dell’utente prima che ne venga attivata la esecuzione. Nella memoria di un elaboratore moderno si possono pertanto individuare in ogni istante cinque aree distinte: - l’insieme 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 l’insieme 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. L’esecutore è 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 l’ambiente 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 dall’altra; - 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 l’altra serialmente con l’unità 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 l’azione 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 un’azione alla volta: pertanto un’istruzione 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 all’interno di un unico circuito integrato le funzioni di un’intera 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 8 16 64
Bit Address BUS 16 20-24 64
Capacità di indirizzamento 64 KByte 1-16 MByte 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 l’indirizzo logico con il quale prelevare o salvare il valore dell’operando 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 all’esecuzione 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 l’esecuzione delle istruzioni consentendo l’attivazione in parallelo di alcune microoperazioni della unità di controllo. L’insieme 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 all’istruzione da prelevare all’inizio della fase a puntatore all’istruzione successiva da eseguire al termine della stessa; - Accumulatore (ACC): utile a conservare dati temporanei o necessario per attivare le operazioni logiche ed aritmetiche dell’ALU; infatti attraverso esso i dati vengono forniti all’ALU prima di una operazione e in esso si trova il risultato dell’operazione eseguita; alcuni microprocessori possono presentare anche due accumulatori; - Condition Code (CC): che riporta lo stato dell’elaborazione 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 l’operazione 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 l’indirizzo dell’operando 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 l’ultimo elemento inserito sia anche il primo ad essere estratto.
70
Capitolo secondo
L’accumulatore 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 l’address bus al quale comunicano l’indirizzo 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 dell’address bus) a 16 bit. I moderni processori operano con registri di accumulatore a 16 o 32 bit e parallelismo dell’address 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 l’istruzione 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 l’address bus del quale rappresenta lo stato, in altri termini il registro AB indica il valore che assume l’address 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 l’architettura di riferimento di un processore dotato dell’insieme di registri indicati con le principali connessioni tra essi. Nella costruzione dell’indirizzo di un operando di una istruzione le tecniche di indirizzamento più diffuse sono: - indirizzamento immediato che indica che il valore è contenuto già nell’istruzione; - indirizzamento diretto con il quale viene riportato nell’istruzione l’indirizzo del registro di memoria che contiene il valore o nel quale depositare il valore; - indirizzamento indiretto che riporta nell’istruzione l’indirizzo del registro di memoria al cui interno è specificato l’indirizzo del registro dal quale prelevare un valore o nel quale depositare un valore; - indirizzamento relativo con il quale l’indirizzo 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 nell’istruzione, o diversificando il codice operativo, o aggiungendo dei bit appositi il cui valore indica alla UC come costruire l’indirizzo. Ad esempio per la semplice istruzione per il caricamento dell’accumulatore si potrebbero avere in linguaggio macchina (rappresentato in esadecimale) le quattro istruzioni di tabella: Cod Op.
Operando
Tecnica
60
00ff
Immediata
61
00ff
Diretta
62
00ff
Indiretta
63
Relativa
Commento
Accessi in memoria nella fase Operand Assembly
LOAD ACC con il valore nessuno in quanto il dato è 255 prelevato nella fase fetch con l’istruzione LOAD ACC con il un solo accesso in contenuto del registro di memoria memoria di indirizzo 255 LOAD ACC con il due accessi in memoria: il contenuto del registro di primo per prelevare memoria il cui indirizzo è l’indirizzo alla posizione contenuto nel registro di indicata; il secondo per indirizzo 255 prelevare il dato 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 dell’accumulatore 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 l’I/O-mapped. Con la tecnica memory mapped l’UC 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 l’I/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 dall’altra 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 l’esecuzione 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 nell’istruzione. 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 l’attività di programmazione offrendo istruzioni più compatte, dall’altra rende anche il processore più complesso.
74
Capitolo secondo
2.7.
Un modello di processore
Da un’analisi 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 un’etichetta per far riferimento ad essa in altre istruzioni, e si può evidenziare nella istruzione la tecnica di indirizzamento scelta. L’assemblatore è 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 Op.
Tecnica Indirizzamento
Operando
Commento
La label non sempre è presente così come il commento a fine frase che serve a spiegare le ragioni per le quali viene introdotta l’istruzione nel programma. Il campo Tecnica Indirizzamento indica la modalità di composizione dell’indirizzo; 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 l’operando 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 dell’ACC 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 l’immissione 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 nell’unico 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 l’accumulatore, 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”). L’ipotesi 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 l’operando, 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 l’UC riconosce dal codice operativo dell’istruzione 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 dell’istruzione. 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 nell’accumulatore 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 l’istruzione successiva il cui indirizzo è specificato in PI, ma con quella indicata dal valore dell’operando secondo modalità diverse. Per ogni istruzione verrà riportato il codice operativo (CodOP) in linguaggio assemblativo, l’operando 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 dall’accumulatore). 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 l’istruzione richiede che venga determinato un valore sulla base di una delle tecniche di indirizzamento specificate: - è proprio OP per l’immediato, - è M(OP) per il diretto, - è M([M(OP)]) per l’indiretto - è M([X]) per il relativo. Per semplificare si indicherà con [OP] il valore determinato. Le istruzioni di gestione dell’accumulatore sono quelle riportate in tabella 4. CodOP Operando Operazione Descrizione copia in ACC il dato come determinato dalla LDA OP ACC = [OP] STA
OP
[OP] = ACC
PSHA
M([SP]) = ACC SP = SP - 1
PULA
SP = SP + 1 ACC = M([SP])
CLRA NEGA
ACC = 0 ACC=0 - [ACC]
tecnica di indirizzamento copia nel registro di memoria, il cui indirizzato è determinato dalla tecnica di indirizzamento, il valore di ACC prima copia ACC nell’area stack di memoria, ossia all’indirizzo contenuto in SP; dopo viene decrementato il valore di SP per consentire un successivo inserimento nell’area dopo aver incrementato il contenuto del registro SP, copia in ACC il contenuto del registro di memoria indicato da SP azzera il contenuto di ACC calcola il complemento alla base del contenuto di ACC
Tabella 4 – Istruzioni per la gestione dell’accumulatore
Il modello di esecutore
77
Le istruzioni di gestione del registro X sono quelle in tabella 5. CodOP Operando Operazione Descrizione copia in X il dato come determinato LDX OP X = [OP] STX
OP
[OP] = X
dalla tecnica di indirizzamento 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 copia in SP il dato come determinato LDSP OP SP = [OP] STSP
OP
[OP] = SP
dalla tecnica di indirizzamento 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 modifica il contenuto INCA ACC = [ACC] +1 DECA
ACC = [ACC] -1
INCX
X = [X] +1
DECX INCS
X = [X] -1 SP = [SP] +1
DECS ADDA
OP
SP = [SP] -1 ACC = [ACC] + [OP]
SUBA
OP
ACC = [ACC] - [OP]
di ACC aggiungendo 1 modifica il contenuto di ACC sottraendo 1 modifica il contenuto di X aggiungendo 1 modifica il contenuto di X sottraendo 1 modifica il contenuto di SP aggiungendo 1 modifica il contenuto di X sottraendo 1 Somma al contenuto di ACC il valore determinato sulla base della tecnica di indirizzamento 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 NOTA ACC = not[ACC] ANDA
OP
ACC = [ACC] and [OP]
ORA
OP
ACC = [ACC] or [OP]
Descrizione complemento di ACC; i bit 1 vengono cambiati in 1 e quelli 0 in 1 Prodotto logico tra il contenuto di ACC e il valore determinato sulla base della tecnica di indirizzamento; l’and viene eseguito sui singoli bit coinvolgendo quelli che occupano la stessa posizione Somma logica tra il contenuto di ACC e il valore determinato sulla base della tecnica di indirizzamento; l’or 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 BITA
Operando OP
Operazione [ACC] and [OP]
CMPA
OP
[ACC] - [OP]
TST
OP
[OP] – 0
TSTA
[ACC] – 0
Descrizione Se Z è diverso da zero, indica che ACC ha i bit uguali a 1 nelle stesse posizioni di OP Se Z è uguale a zero ACC è OP hanno la stessa configurazione di bit Se OP è uguale a zero Z è uguale ad 1 Se OP è negativo S è uguale ad 1 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 JMP Address PI = Address BRZ
Address
Z=1
PI = Address
BRNZ
Address
Z=0
PI = Address
BRS
Address
S=1
PI = Address
BRNS
Address
S=0
PI = Address
BRO
Address
O=1
PI = Address
BRNO
Address
O=0
PI = Address
BRC
Address
C=1
PI = Address
BRNC
Address
C=0
PI = Address
Descrizione Salto all’istruzione presente all’indirizzo specificato Se il bit Z è 1 salta all’istruzione all’indirizzo specificato altrimenti prosegui in sequenza Se il bit Z è 0 salta all’istruzione all’indirizzo specificato altrimenti prosegui in sequenza Se il bit S è 1 salta all’istruzione all’indirizzo specificato altrimenti prosegui in sequenza Se il bit S è 0 salta all’istruzione all’indirizzo specificato altrimenti prosegui in sequenza Se il bit O è 1 salta all’istruzione all’indirizzo specificato altrimenti prosegui in sequenza Se il bit O è 0 salta all’istruzione all’indirizzo specificato altrimenti prosegui in sequenza Se il bit C è 1 salta all’istruzione all’indirizzo specificato altrimenti prosegui in sequenza Se il bit C è 0 salta all’istruzione all’indirizzo 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 dall’istruzione. Se la condizione non viene verificata, il PI non viene modificato e l’esecuzione continua con l’istruzione successiva a quella di salto. Le istruzioni di gestione dei sottoprogrammi sono in tabella 11. CodOP Operando Operazione Descrizione (Jump to subroutine) JSR Address M([SP]) = PI Salto all’istruzione indicata da address SP = [SP] - 1 dopo aver salvato il valore di PI PI = Address nell’area stack RTS
SP = [SP] + 1 PI = M([SP])
(Return from subroutine) Salta all’istruzione il cui indirizzo viene prelevato dall’area 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 dall’esterno (per inserimento da tastiera) dal canale di standard] OUT
output [ACC]
standard
=
input viene depositato in ACC Il valore di ACC viene fornito al canale di output per essere mostrato all’esterno (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 dall’obbligo 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: - un’area istruzioni necessaria per contenere l’intero programma; - un’area dati per gestire i valori delle informazioni da trattare; - un’area stack importante per gestire sottoprogrammi e interruzioni.
80
Capitolo secondo
Le tre aree devono essere adeguatamente dimensionate affinché non capiti che durante l’esecuzione del programma una di esse sconfini nelle altre. Se ciò accadesse si avrebbero effetti non prevedibili e di difficile diagnosi. Ad esempio, se durante l’esecuzione alcuni dati venissero memorizzati nell’area 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 dell’area di stack a scapito dell’area 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 dell’algoritmo, e tale registro viene ad essere modificato per effetto della espansione dell’area stack, si ha come naturale conseguenza la generazione di una condizione di errore. La memoria viene pertanto suddivisa ponendo: - l’area istruzioni ad indirizzi bassi in quanto l’allocazione delle istruzioni del programma deve essere fatta ad indirizzi consecutivi crescenti; - l’area stack ad indirizzi alti in quanto le operazioni di inserimento avvengono decrementando lo SP - l’area 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 l’esecuzione 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 l’area stack. Un primo e semplice programma per il nostro MP è quello in tabella 15. IM 1 2
Label
CodOp LDSP LDA
3 4 5 6
OUT IN STA LDA
7 8 9
OUT IN ADDA
10
TI = =
^ =
Operando 999 “Inserire dato” 500 “Inserire dato”
Commento posizionamento iniziale di SP carica una stringa in accumulatore visualizza messaggio input di un dato da tastiera conserva primo dato carica una stringa in accumulatore
visualizza messaggio input di un secondo dato da tastiera sommalo al primo dato prelevato da ^ 500 tastiera presenta il risultato al monitor OUT Tabella 15 – Esempio di programma per MP
Per comodità è stata inserita la colonna IM (indirizzo di memoria) per indicare l’allocazione 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: l’insieme degli stati nell’ordine 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 sull’output standard per effetto di operazioni d OUT; - il file di input (FI) dove sono elencati i dati di prova che possono essere inseriti dall’input standard per effetto di operazioni di IN. Nel caso in cui si fissi che i programmi siano caricati a partire dall’indirizzo 1, l’area dati inizi all’indirizzo 500, l’area 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 dell’UC. PI IR ACC SZOC X SP M(500) M(501) M(502) M(503) M(997) M(998) M(999) FO 2 LDSP 999 =999 3 LDA Inserire 0000 “=Inseri dato re dato” 4 OUT Inserire dato 5 IN 33 6 STA 33 ^500 7 LDA Inserire “=Inseri dato re dato” 8 OUT Inserire dato 9 IN 22 10 ADDA 55 ^500 11 OUT
FI
33
22 55
Tabella 16 – Tabella di trace
Si noti che per comodità di lettura nelle celle vengono riportati solo i cambiamenti sottintendendo che l’ultimo 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 l’ultima 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 l’istruzione 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 info01 info02 buffer
Indirizzo di memoria 500 501 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 1 2
Label
CodOp LDSP LDA
3 4 5 6
OUT IN STA LDA
7 8
OUT IN
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
scambio
Stampa
Fine
STA LDA STA LDA STA LDA STA LDA OUT LDA OUT LDA OUT LDA OUT JMP
TI = =
Operando 999 “Dato 1>”
^ =
info01 “Dato 2>”
^ ^ ^ ^ ^ ^ ^ =
info02 info1 buffer info2 info01 buffer info02 “Dato1:”
^
info01
=
“Dato2:”
^
info02 sis_op
Commento posizionamento iniziale di SP carica una stringa in accumulatore visualizza messaggio input di un dato da tastiera conserva primo dato carica una stringa in accumulatore visualizza messaggio input di un secondo dato da tastiera conserva secondo dato inizia scambio conserva il primo valore preleva il secondo valore copialo nel primo registro riprendi il primo valore portalo nel secondo registro carica messaggio visualizza messaggio visualizza valore carica messaggio visualizza messaggio visualizza valore salta all’indirizzo sis_op
Tabella 18 – Esempio di programma per MP
Il modello di esecutore
83
Il trace del programma è quello riportato nella tabella 19. PI 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
IR LDSP =999 LDA = “Dato1>” OUT
ACC
Dato 1>
info 02
buffer M(997) M(998) M(999) FO FI
Dato 1>
IN 33 STA ^info01 Dato LDA 2> = “Dato 2>” OUT IN STA ^info02 LDA ^info01 STA ^buffer LDA ^info02 STA ^info01 LDA ^buffer STA ^info02 LDA =”Dato 1:” OUT
SZO X SP info C 01 99 9 0000
33 33
Dato 2>
22
22 22
33 33 22 22 33 33 Dato 1: Dato 1:
LDA 22 ^info01 OUT LDA Dato 2: =”Dato 2:” OUT LDA ^info02 OUT
22 Dato 2:
33 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 all’indirizzo specificato dopo aver conservato nell’area 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 l’esecuzione con le istruzioni che compongono il sottoprogramma. L’istruzione RTS, posta al termine del sottprogramma, ripristina nel PI il valore che era stato conservato all’atto del JSR riprendendo in tale modo l’esecuzione del programma chiamante.
84
Capitolo secondo
Nella figura seguente si è organizzato come sottoprogramma la sequenza formata dalla stampa il valore dell’accumulatore e dall’input 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 LDSP =999
ACC SZOC X SP dato1 dato2 M(997) M(998) M(999) FO 99 9 Dato 0000 1>
102
LDA = “Dato 1>” JSR
301
OUT
302 102
IN RTS
103
105
STA ^dato1 LDA = “Dato 2>” JSR
301
OUT
302
IN
2 101
104
300
99 8
FI
102 Dato 1>
33
33 99 9 33
Dato 2> 300
99 8
105 Dato 1>
33
33
Il modello di esecutore 102
RTS
10
STA ^dato2
85 99 9 22
Tabella 20 – Tabella di trace
L’introduzione dei sottoprogrammi obbliga il programmatore a pianificarne l’allocazione in memoria in area diversa da quella occupata dal programma principale. Per semplificare il lavoro del programmatore si potrà associare un nome all’indirizzo 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, l’inserimento 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 Vettore
Indirizzo Memoria 600
Tipo variabile
n i totale max min leggi conta in_vet somma minimo massimo
700 701 702 703 704 100 110 120 150 200 250
variabile variabile variabile variabile variabile label label label label label label
Descrizione inizio sequenza dati (si assume che possano essere massimo 100) numero dati inseriti indice di ciclo valore somma valore massimo valore mnimo sottoprogramma sottoprogramma sottoprogramma sottoprogramma sottoprogramma sottoprogramma
Tabella 21 – Tabella dei simboli
si può procedere con la programmazione.
86
Capitolo secondo
IM 100 101 102 103
Label LEGGI
CodOp OUT IN STA RTS
TI
Operando
(X)
Commento stampa messaggio leggi dato memorizza dato 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 l’indirizzo di memoria dove conservare il dato preso da tastiera. Al suo termine il dato inserito da tastiera rimane in ACC e si trova anche all’indirizzo 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 120 121 122 123 124 125
Label IN_VET
CICLO
Cod Op
LDX CLRA STA LDA JSR BRZ
126
INCX
127 128 129
JSR JMP RTS
FINE_CICLO
TI =
Operando vettore
^ =
n “Inserisci” LEGGI FINE_CICLO
CONTA CICLO
Commento carica in X indirizzo di vettore azzera contatore carica messaggio Salta a sottoprogramma salto condizionato alla presenzadi uno zero in ACC (terminazione sequenza di input) se il salto non avviene il dato inserito è diverso da zero e si deve procedere al successivo inserimento in una posizione di memoria seguente incrementa contatore riprendi l’inserimento 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
IM 150
CodOp LDX
TI =
Operando vettore
Commento carica in X indirizzo di vettore
CLRA STA STA LDA ADDA
^ ^ (X) ^
tot i
156
STA
^
tot
157
INCX
158 159 160 161
LDA INCA STA CMPA
^
i
^ ^
i n
162
BRNZ
azzera somma salva valore indice preleva dato da sequenza aggiungi la somma precedente conserva il valore calcolato incrementa la posizione nella sequenza carica valore indice incrementa salva valore indice confronta con il numero di elementi se non sono uguali continua la somma
163
RTS
151 152 153 154 155
IM 200 201 202 203 204 205
Label SOMMA
87
CICLO_SOM
Label MINIMO
CICLO_MIN
206 207 208 209 210 211 212 213 214
CONTINA
215 216 IM 250 251
Label MASSIMO
tot
CICLO_SOM
Tabella 24 – Programma SOMMA CodOp TI Operando Commento LDX = vettore carica in X indirizzo di vettore LDA (X) preleva primo dato da sequenza STA ^ min posiziona in valore min LDA = 1 STA ^ i posiziona ad 1 indice INX incrementa la posizione nella sequenza LDA (X) preleva dato da sequenza SUBA ^ min fai la differenza con min BRNS CONTINUA se la differenza non è negativa l’elemento èpiù grande del presunto minimo LDA (X) riprendi il dato STA ^ min aggiorna minimo LDA ^ i carica valore indice INCA incrementa STA ^ i salva valore indice CMPA ^ n confronta con il numero di elementi BRNZ CICLO_MIN se non sono uguali continua i confronti RTS Tabella 25 – Programma MINIMO CodOp TI Operando Commento LDX = vettore carica in X indirizzo di vettore LDA (X) preleva primo dato da sequenza
88 252 253 254 255
Capitolo secondo STA LDA STA INX
^ = ^
max 1 i
256 257
LDA SUBA
^ (X)
max
258
BRNS
259 260 261 262 263 264 265
LDA STA LDA INCA STA CMPA BRNZ
266
CICLO_MAS
PROSEGUI
PROSEGUI (X) ^ ^ ^ ^
max i i n CICLO_MAS
posiziona in valore max posiziona ad 1 indice incrementa la posizione nella sequenza preleva max fai la differenza con dato da sequenza se la differenza non è negativa il presunto max è più grande dell’elemento della sequenza riprendi il dato aggiorna massimo carica valore indice incrementa salva valore indice confronta con il numero di elementi se non sono uguali continua i confronti
RTS Tabella 26 – Programma MASSIMO
IM
Si può adesso scrivere il programma principale. 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 dall’esempio è che i richiami dei sottoprogrammi possono essere nidificati: ossia all’interno di un sottoprogramma si possono richiamare altri sottoprogrammi. Il collegamento tra le diverse componenti del programma è garantito dalla gestione dell’area di memoria attraverso il registro SP. Ad ogni salto al sottoprogramma viene inserito (operazione di PUSH) l’indirizzo di ritorno nell’area stack di memoria. L’istruzione RTS preleva (operazione di POP)
Il modello di esecutore
89
l’ultimo indirizzo inserito a cui tornare. I push conservano gli indirizzi di ritorno nell’ordine in cui si sono determinati; i pop li prelevano nell’ordine inverso.
Figura 30 – Gestione sottoprogrammi nidificati
L’assemblatore è 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 l’assemblatore 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 all’assemblatore direttive con le quali guidare la traduzione. Per esempio può indicare all’assemblatore: - 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 l’area 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 l’esecuzione 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 l’esempio 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 JSR LDA OUT JSR LDA OUT JSR LDA OUT
^
IN_VET SOMMA totale
^
MINIMO min
^
MASSIMO max
#STOP AREAPROG AT 300 LEGGI
CONTA
OUT IN STA RTS LDA INCA STA RTS
IN_VET
LDX CLRA STA CICLO LDA JSR BRZ INCX JSR JMP END_CICLO RTS SOMMA
LDX CLRA STA STA CICLO_SOM LDA ADDA
(X) ^
n
^
n
=
vettore
^ =
n “Inserisci” LEGGI END_CICLO CONTA CICLO
=
vettore
^ ^ (X) ^
tot i tot
Il modello di esecutore STA INCX LDA INCA STA CMPA BRNZ RTS MINIMO
LDX LDA STA LDA STA CICLO_MIN INX LDA SUBA BRNS LDA STA CONTINUA LDA INCA STA CMPA BRNZ RTS MASSIMO
LDX LDA STA LDA STA CICLO_MAS INX LDA SUBA BRNS LDA STA PROSEGUI LDA INCA STA CMPA BRNZ RTS
91 ^
tot
^
i
^ ^
i n CICLO_SOM
= (X) ^ = ^
vettore
(X) ^ (X) ^ ^
min 1 i min CONTINUA min i
^ ^
i n CICLO_MIN
= (X) ^ = ^
vettore
^ (X) (X) ^ ^ ^ ^
max 1 i max PROSEGUI max i i n CICLO_MAS
L’assemblatore inserirà in automatico le istruzioni: LDS = address_area_stack JMP start_address all’inizio del programma, e: JMP sistema_operativo al posto della direttiva #STOP.
92
Capitolo secondo
L’ultima 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 l’ipotesi 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 dell’intero nastro di carta. La CPU restava a lungo inutilizzata in quanto iniziava l’esecuzione solo quando il pulsante di RUN veniva premuto dall’operatore di sistema. E anche l’architettura del processore MP potrebbe funzionare nello stesso modo. Si può però costruire un programma che affidi proprio a MP l’onere di effettuare il caricamento in memoria delle istruzioni di un altro programma prelevandole dall’input 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 l’UTENTE.
Figura 31: MP e caricamento programmi
Ad esempio il LOADER per l’architettura MP è caratterizzato da: - occupazione delle prime posizioni di memoria sia per area codice che dati; - prima istruzione da eseguire collocata all’indirizzo uno; - caricamento delle programma UTENTE fissata con inizio a partire dall’indirizzo 100;
Il modello di esecutore
-
93
riconoscimento di MEM come posizione a partire dalla quale iniziare il caricamento; per semplicità si assume che l’indirizzo 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 l’assemblatore 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 nell’ipotesi che il programma da caricare sia quello di somma di due numeri. Si supponga che tale programma sia prodotto dall’assemblatore che ha inserito le direttive MEM e END. Inoltre, per rendere più leggibile l’esempio si sono mantenute le istruzioni in linguaggio assemblativo invece di riportarne il codice binario prodotto dall’assemblatore. PI 2
IR
ACC SZOC X SP M(80) M(81) M(82) M(83) M(84) M(85) M(100) M(101) M(200)
4
LDSP =99 LDA Prompt> 0000 =”Prompt>” STA ^80
5
LDA =100
6
STA ^81
3
99
Prompt>
100 100
FO
FI
94 7
Capitolo secondo LDA =”MEM” STA ^82
MEM
LDA =”END” 10 STA ^83
END
8 9
MEM
END
11 LDX ^81 12 LDA ^80
100 Prompt>
13 OUT 14 IN 15 STA ^84
Prompt> LDSP ^999
16 CMPA ^83
LDSP^999 LDSP ^999
1000
17 BRZ 28 18 LDA ^84 19 CMPA ^82
LDSP ^999
0000 1000
20 BRNZ 24 25 LDA ^84 26 STA (X)
LDSP ^999
0000
27 INCX
LDSP ^999
101
28 JMP 11 12 LDA ^80
Prompt>
13 OUT 14 IN 15 STA ^84
Prompt> LDSP ^999
16 CMPA ^83
JMP100 LDSP ^999
1000
17 BRZ 28 18 LDA ^84
JMP100 0000
19 CMPA ^82
1000
20 BRNZ 24 25 LDA ^84
JMP100 0000
26 STA (X) 27 INCX
JMP 100
102
28 JMP 11 12 LDA ^80
Prompt>
13 OUT 14 IN
Prompt> MEM
MEM
15 STA ^84
MEM
16 CMPA ^83
1000
17 BRZ 28 18 LDA ^84 19 CMPA ^82
MEM
0000 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 14 IN
Prompt> IN
IN
15 STA ^84
IN
16 CMPA ^83
1000
17 BRZ 28 18 LDA ^84
IN
19 CMPA ^82
0000 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 14 IN
Prompt> 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 12 LDA ^80
100 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 all’atto dell’accensione 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 l’ipotesi introdotta riguardante la strutturazione dei registri in contenitori di informazioni di qualsiasi tipo. L’esempio che segue confronta una istruzione di MP con un caso reale di processore che opera con registri di memoria a byte. Nell’esempio si è fatto ricorso ai codici ASCII dei caratteri che compongono il messaggio. Processore MP LDA =”CIAO” OUT
Processore a Byte LDA =”C” OUT LDA =”I” OUT LDA =”A” OUT LDA =”O” OUT
Si comprende dall’esempio che l’ipotesi è stata introdotta solo per la semplificazione degli esempi.
Capitolo terzo
Algoritmi e programmi
3.1.
Informatica come studio di algoritmi
Aver definito l’informatica come la scienza e la tecnica dell’elaborazione dei dati o, più in generale, del trattamento automatico delle informazioni, porta inevitabilmente a riflettere sul fatto che per elaborare l’informazione sia necessario preliminarmente comprendere il modo in cui procedere nella sua elaborazione. In un tale contesto l’informatica 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 è nell’ordine: - l’analisi dettagliata di ciò che serve al trattamento dell’informazione, - 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 l’informatica 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 dell’attenzione, sebbene l’interesse principale dell’informatica è quello di rendere comprensibili ed eseguibili gli algoritmi per un sistema di calcolo. Il calcolatore è tra tutti gli esecutori di algoritmi (compreso l’uomo) 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 l’informatica va sempre più a rivestire nelle società evolute. Lo studio dell’Informatica 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 l’informatica 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 l’uomo 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 l’altro, 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 all’interno 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
-
-
3.1.2.
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 l’esecutore non solo deve comprendere le singole istruzioni ma deve essere anche capace di eseguirle. E può eseguirle una dopo l’altra secondo un ordine rigidamente sequenziale che impone l’inizio dell’esecuzione 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.
La calcolabilità degli algoritmi
L’algoritmo è composto da una sequenza finita di passi che portano alla risoluzione automatica di un problema. Il singolo passo è un’azione 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. L’automa a stati finiti fornisce un’astrazione 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. L’automa è uno dei modelli fondamentali dell’informatica 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, l’effetto 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 l’evoluzione 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 l’unico 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 l’indicazione dell’eventuale 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 l’insieme 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 dell’informatica, 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 un’azione elaborativa.
Figura 3 – Macchina di Turing
Formalmente la macchina di Turing è definita dalla quintupla: A, S, fm , fs, fd dove: -
A è l’insieme finito dei simboli di ingresso e uscita; S è l’insieme 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; D = {Sinistra, Destra, fd è la funzione di direzione A × S 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 l’algoritmo per l’elaborazione 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 dell’algoritmo è una macchina universale capace di simulare il lavoro compiuto da un’altra macchina qualsiasi. Ma leggere dal nastro la descrizione dell’algoritmo richiede di saper interpretare il linguaggio con il quale esso è stato descritto. La Macchina di Turing Universale diventa così l’interprete 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; - l’algoritmo è 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 è un’ipotesi 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 dell’interazione attraverso i suoi dispositivi di input ed output.
Algoritmi e programmi
3.1.3.
103
La trattabilità degli algoritmi
Calcolabilità e trattabilità sono due aspetti importanti dello studio degli algoritmi. Mentre la calcolabilità consente di dimostrare l’esistenza 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 all’algoritmo 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 l’efficienza 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 all’ambiente circostante. Tra due soluzioni di uno stesso problema una soluzione è più efficiente dell’altra 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) dell’algoritmo e prescindono dai dati di input su cui esso opera. Sono invece misure dinamiche quelle che tengono conto sia delle caratteristiche strutturali dell’algoritmo che dei dati di input su cui esso opera. Un primo fattore che incide sul tempo impiegato dall’algoritmo per produrre un risultato, o tempo di esecuzione, è sicuramente la quantità di dati su cui l’algoritmo 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 dell’input. 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 un’analisi molto utile, perché fornisce delle garanzie sul tempo massimo che l’algoritmo 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 l’ordine di grandezza del tempo di esecuzione cresce al limite, ossia per dimensioni dell’input 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 l’attenzione 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 dell’input oltre la quale l’ordine di grandezza del tempo di esecuzione del primo algoritmo è inferiore all’ordine di grandezza del tempo di esecuzione del secondo. La complessità asintotica dipende solo dall’algoritmo, mentre la complessità esatta dipende da tanti fattori legati alla esecuzione dell’algoritmo. 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).
n n2 n3 n5 2n 3n
10 0,00001 sec 0,0001 sec 0,001 sec 0,1 sec 0,001 sec 0,059 sec
20 0,00002 sec 0,0004 sec 0,008 sec 3,2 sec 1,0 sec 58 min
30 0,00003 sec 0,0009 sec 0,027 sec 24,3 sec 17,9 min 6,5 anni
40 0,00004 sec 0,0016 sec 0,064 sec 1,7 min 12,7 giorni 3,855 secoli
50 0,00005 sec 0,0025 sec 0,125 sec 5,2 min 35,7 anni 200.000.000 secoli
Tabella 1 – Complessità temporale
Si nota che gli algoritmi di tipo polinomiale si comportano meglio di quelli esponenziali. Ma l’aspetto 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
3.2.
105
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 un’istruzione 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 nell’esempio 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 l’effetto 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 dell’informatica 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 l’altro l’uso 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à dell’esecutore 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 l’algoritmo nella versione da affidare all’esecutore per l’esecuzione.
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 l’esecuzione dell’operazione; - 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 l’intero 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
x1
b d 2a
Calcolo la seconda radice come
x2
b d 2a
Esempio 1 – Algoritmo per il calcolo delle radici di un’equazione 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. L’analisi delle sequenze dinamiche serve proprio a capire se un dato algoritmo abbia o meno una terminazione. Se si modifica l’algoritmo 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
0 Se Allora Calcolo
d
Calcolo la prima radice come
b d 2a
x1
Calcolo la seconda radice come
b d 2a
x2
Altrimenti Calcola radici complesse Esempio 2 – Algoritmo per il calcolo delle radici di un’equazione 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
0 è risultata vera Verifico che Calcolo d Calcolo la prima radice come
b d 2a
x1
Calcolo la seconda radice come
x2
b d 2a
Calcolo
b2
4ac
0 è risultata non vera Verifico che Calcolo radici complesse Esempio 3 – Possibili sequenze di esecuzione per l’algoritmo per il calcolo delle radici di un’equazione 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 l’algoritmo 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 nell’esempio 5, mentre, se sono uguali al secondo tentativo si ha a sequenza di esecuzione dell’esempio 6, o dopo n tentativi usando una notazione più sintetica si ha la sequenza nell’esempio 7. Infine, el caso sfortunato di non trovare mai quattro carte uguali si ha la sequenza di esecuzione nell’esempio 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 l’algoritmo 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 l’algoritmo 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 l’algoritmo 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 l’algoritmo 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 ( >=
<
-
6.5.
AND bit a bit OR bit a bit XOR shift a sinistra shift a destra complemento ad uno
La specifica dell’algoritmo 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). L’istruzione di assegnazione termina con il delimitatore punto e virgola.
Figura 16 – Carta sintattica per un’istruzione di assegnazione
Un’espressione, 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 l’istruzione del tipo: Espressione1 = Espressione1 operando Espressione2
può essere anche espressa con: Espressione1 operando= Espressione2
Ad esempio, l’espressione x = x+1 diventa: x + = 1;
mentre l’espressione 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 l’espressione1: se essa è diversa da zero (vera) allora viene valutata l’espressione2, altrimenti viene valutata l’espressione3. Ad esempio l’espressione: 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 d’uscita.
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 l’if, 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 l’azione elaborativa dell’if o dell’else 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 un’ulteriore istruzione di selezione. Nel caso di costrutti innestati il C dispone di una regola che assegna ogni else all’if più vicino che non ne possiede già uno. Un esempio è il seguente, dove l’else è 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 L’istruzione 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 l’istruzione break causa l’uscita 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 l’appartenenza 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à dell’espressione, 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 all’inizio del ciclo (loop) stesso, il costrutto do-while la verifica alla fine, con la conseguenza che esso viene eseguito almeno una volta. In particolare, l’istruzione viene eseguita, poi viene valutata l’espressione: se è vera, l’istruzione viene ancora eseguita e così via. Il ciclo termina quando l’istruzione 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 un’istruzione di assegnazione con la quale viene fornito il valore iniziale alla variabile di controllo del ciclo. Lo statement di condizione rappresenta un’espressione 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 l’aggiornamento consiste in un incremento o decremento del valore della variabile di controllo. Esempi di for sono: for(int i=0; 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=posiz;i--) { v[i+1]=v[i]; } v[posiz]=info; // aggiorna il riempimento *n=*n+1; }
Esempio d’uso: Per testare la funzione sviluppata può essere utilizzato il seguente main in cui è inserita anche la gestione dell’I/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 ((nNMAX-1)) { printf("Inserisci il numero delle componenti del tuo vettore(>=0 =0
View more...
Comments