Sei sulla pagina 1di 113

RAPPRESENTAZIONE dell'INFORMAZIONE

Introduzione
Nei calcolatori elettronici le informazioni possono essere di tipo tradizionale ( testi, numeri
interi e reali) e di tipo multimediale ( immagini, suoni, video).

Per poter rappresentare le informazioni è necessario codificarle e per poterne garantire


l'affidabilità nella loro trasmissione è necessario che la probabilità di errore sia minima. Ciò è
possibile se l'insieme dei simboli usati è il più piccolo possibile.
Infatti per rappresentare le informazioni all'interno dei calcolatori si usano due soli simboli,
ciò è possibile grazie al fatto che vengono usati fenomeni fisici relativamente semplici che
possono essere ridotti a due stati.
I simboli possono essere rappresentati da:

• una tensione elettrica {Vhigh (alta), Vlow (bassa)};

• un differente stato di polarizzazione magnetica (positiva,


negativa);

• luce e buio.

I valori ad essi associati sono 0 e 1 e vengono detti bit (binary digit); tutti i dati (numeri,
parole, in generale oggetti) sono rappresentati da sequenze di bit.
Per cui ogni informazione viene rappresentata nel calcolatore in forma binaria.
Sistemi di numerazione

Numeri naturali
Noi rappresentiamo i numeri con una notazione posizionale che utilizza le10 cifre 0,1,..9
(numeri arabici).
Un numero è rappresentato da una sequenza di cifre, posizionale significa che il valore di ogni
cifra dipende dalla sua posizione all'interno della sequenza.

E' possibile scegliere il numero di cifre differenti che si usano in una notazione posizionale, e
tale numero prende il nome di base; in ogni numero decimale (in base dieci) la cifra
all'estrema destra ha il valore minore (cifra meno significativa) , quella all'estrema sinistra il
valore maggiore (cifra più significativa).
In genere si usano anche numerazione binaria in base 2, ottale in base 8, esadecimale in base
16.

Codifica dei numeri naturali

Per una numerazione in base p, con p un numero naturale > 1 occorrono p cifre distinte, siano
esse C1, ..., Cp ed un numero è rappresentato come una sequenza di tali cifre.
Sia una rappresentazione di un numero in base p, esso rappresenta

dove rappresenta il numero d'ordine della cifra ([C1] = 0, ..., [Cp] = p-1).

Nel sistema binario si usano le cifre 0 e 1. La base 2 è quella più piccola teoricamente possibile
per un sistema di numerazione. Il valore posizionale è legato alle potenze di 2.

Nel sistema ottale si usano 0,1,2,3,4,5,6,7. Il valore posizionale è legato alle potenze di 8.
Nel sistema esadecimale si usano 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F. Il valore posizionale è legato
alle potenze di 16.

Esempi di conversione da base p a base decimale


• Esempio 1:
Codifica decimale p=10
N10=5870
5870 = 5 * 103 + 8 * 102 + 7 * 101 + 0 * 100
• Esempio 2:
Codifica binaria p=2
N2=101001011
1010010112=1 * 28 + 0 * 27 + 1 * 26 + 0 * 25 + 0 * 24 + 1 * 23+0 *
22+1 * 21+1*20= 33110

• Esempio 3:
Codifica ottale p=8
N8=5348
5348= 5 * 82+3 * 81+4= 34810

• Esempio 4:
Codifica esadecimale p=16
N16=B7F
B7F16= 11 * 162+7 * 161+15= 294310
Rappresentazione dei numeri

Introduzione
Nella rappresentazione dei numeri attraverso il sistema binario sorgono due problemi:

• rappresentazione dei numeri troppo grandi o troppo piccoli.


E' possibile rappresentare solo i numeri compresi in un intervallo
finito, compreso tra la soglia minima di underflow e la soglia
massima di overflow.

• rappresentazione dei numeri periodici e dei numeri razionali.


E' possibile rappresentare solo una parte, ovviamente la più
significativa incorrendo in un errore di troncamento.

In generale nei calcolatori il tipo numerico più corto usato utilizza 16 bit permettendo 65536
combinazioni. Con esso però non si rappresentano i numeri naturali da 0 a 65535, ma i
numeri relativi (con segno) da -32768 a +32767.
Come vengono trasformate le varie combinazioni nei vari numeri?

combinazioni di bit -> numeri relativi

Considerando 16 bit, il primo bit di questi, detto bit più significativo (MSB Most Significant
Bit) a disposizione viene utilizzato per il segno:

1 -> numero negativo;

0 -> numero positivo.

Allora se il primo bit é 0, i rimanenti 15 bit rappresentano un numero positivo da 0 a 32767:

0000 0000 0000 0000 equivale a 0


0000 0000 0000 0001 equivale a 1
0000 0000 0000 0010 equivale a 2
...
0111 1111 1111 1111 equivale a 32767

Se il primo bit é 1 i rimanenti 15 bit li possiamo usare per rappresentare 32768 numeri
negativi.

Questa rappresentazione dei numeri negativi viene detta rappresentazione in complemento a 2


ed é stata inventata per evitare la presenza di due zeri (uno zero positivo ed uno negativo) e
per utilizzare tutte le possibili 65536 combinazioni.

E' possibile rappresentare i numeri interi anche utilizzando 32 bit.


Con questa rappresentazione si può spaziare da -2.147.483.648 a +2.147.483.647. Il modo con
cui viene fisicamente sviluppata questa rappresentazione é del tutto analogo al caso a 16 bit.
Codici e Rappresentazione dei caratteri
Un codice è un sistema di simboli che permette la rappresentazione dell'informazione.
Infatti per rappresentare i vari caratteri è necessario associare attraverso l'uso di un codice ad
ogni numero del codice una sequenza di bit, utilizzati per la trasmissione o la memorizzazione
elettronica.

Si dice binario un codice che ha soli due simboli (ad esempio 0 e 1).
I codici BCD ( Binary Coded Decimal) sono utilizzati per rappresentare i numeri del sistema
numerico decimale codificandoli cifra per cifra.
Per rappresentare le dieci cifre decimali, un codice BCD richiede almeno 4 bit. Lo scopo della
codifica è quello di permettere l'immediata conversione da decimale a binario e viceversa. I
calcoli sono eseguiti in decimale anche se questo comporta una maggiore complessità rispetto
ai calcoli con numeri codificati in complemento a due o in floating point. La maggiore
complessità è dovuta al fatto che dei sedici valori codificabili con 4 bit, solo 10 ne vengono
usati e i riporti e i prestiti nelle operazioni devono essere gestiti in maniera diversa nel
passaggio da ogni gruppo di 4 bit a quelli adiacenti.
Inoltre si ha l'inconveniente nel codice BCD dell'uso inefficiente della memoria: la
memorizzazione dei numeri in decimale richiede molto più spazio rispetto la memorizzazione
in binario.

Attualmente sono in uso pochi sistemi di codifica e sono degli standard.

Il più noto è lo standard ASCII (American Standard Code for Information Interchange) codifica
128 simoli, utilizzando 7 bit (27 = 128 ), numerati in decimale.
I primi 32 caratteri non sono stampabili o visualizzabili e servono per il controllo della
visualizzazione a video, della stampa, ecc.; i rimanenti caratteri sono visualizzabili e
comprendono le cifre, i caratteri , la punteggiatura e i simboli particolari.

Tavola ASCII

Standard più moderni, utilizzati per esempio nelle pagine WEB, sono:

• ISO LATIN 1

• Questo codice usa 8 bit, quindi 256 caratteri diversi.

• Oltre ai caratteri ASCII, ISO Latin-1 contiene vari caratteri


accentati e lettere usate dai linguaggi dell’Europa occidentale
(Italiano, Francese, Spagnolo, Tedesco, Danese, etc): ¡ ¢ £ ¤ ¥ ¦ § ¨
©ª«¬ ®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊË
ÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíî
ïðñòóôõö÷øùúûüýþÿ

• ISO 10646

• E' uno standard internazionale che definisce lo UCS (Universal


Character Set).
• Estende ISO Latin-1 nello stesso senso in cui ISO Latin-1 estende
ASCII.

• UNICODE

• E' uno standard definito dal consorzio Unicode (fondato nel 92) e
definisce un repertorio e una codifica pienamente compatibile con
ISO 10646.

• La versione 3.0 di UNICODE (Febbraio 2000) definisce 49194


caratteri.

• Il vantaggio di UNICODE è di definire un insieme di caratteri


adatti a trattare tutti i linguaggi.

• Supportato da vari SO e internet browsers più recenti.

Algebra booleana
L'algebra di Boole segue le regole della logica con le variabili binarie. Poichè il funzionamento
di tutti i moderni elaboratori è basato su elementi che possono assumere solo due possibili
stati, l'algebra di Boole costituisce il fondamento teorico e pratico di tali macchine.
I due possibili stati che possono assumere le variabili binarie sono tali che si escludono a
vicenda: una variabile può assumere o il valore falso o il valore vero, o lo zero o l'uno. Questi
valori, all'interno dell'architettura dei calcolatori, sono abbinati, entro opportune tolleranze, a
due tensioni differenti denominate livello logico alto e livello logico basso.

Le operazioni fondamentali dell'algebra di Boole sono tre: la negazione, la somma logica e il


prodotto logico. Attraverso queste è possibile realizzare tutte le altre operazioni più complesse
che un calcolatore è in grado di compiere.

Poichè l'algebra di Boole e i calcolatori stessi sono basati su variabili binarie diventa
necessario esprimere tutte le variabili, numeriche e non in uso negli elaboratori, attraverso
combinazioni di zeri e di uni: tale rappresentazione sostituisce al comune sistema decimale,
con il quale siamo abituati a operare quotidianamente, il cosiddetto sistema binario.
ESERCIZI
Sistemi di numerazione

• Esercizi relativi alle conversioni

• Esercizi relativi alle operazioni

1. Rappresentazione dei numeri

• Esercizi relativi ai numeri interi

• Esercizi relativi ai floating point e numeri frazionari

1. Algebra di Boole

• Esercizi relativi all'algebra Booleana e Funzioni logiche


Esercizi relativi alle operazioni
Effettuare le seguenti operazioni tra numeri binari, ipotizzando di lavorare con un
elaboratore con lunghezza di parola (word) pari a un byte (8 bit):

Esercizio n°1
35 - 22

Esercizio n°2
42 + 31

Esercizio n°3
7 * 12

Esercizio n°4
23 / 7

Per poter risolvere le quattro operazioni è necessario fare alcune considerazioni:

In primo luogo, convertiamo in binario i valori indicati, aggiungendo eventuali zeri in testa
per ottenere stringhe di otto bit. Per farlo, sia a scopo di esercizio, sia per velocizzare i calcoli,
convertiamo dapprima il valore decimale in esadecimale, convertendo successivamente
quest'ultimo in binario.
Avremo allora:
35 = 2316 = 0010 0011 (perché 2 * 0010, e 3 * 0011)
22 = 1616 = 0001 0110 (perché 1 * 0001, e 6 * 0110)
42 = 2A16 = 0010 1010 (perché 2 * 0010, e A * 1010)
31 = 1F16 = 0001 1111 (perché 1 * 0001, e F * 1111)
7 = 716 = 0000 0111 (perché 0 * 0000, e 7 * 0111)
12 = 0C16 = 0000 1100 (perché 0 * 0000, e C * 1100)
23 = 1716 = 0001 0111 (perché 1 * 0001, e 7 * 0111)
Utilizzando adesso le regole esposte nella pagina relativa alle operazioni si possono calcolare i
vari risultati.

Soluzione n°1
3510 - 2210 = 0010 00112 - 0001 01102

Passo I:

0010 0011 -
0001 0110 =
-------------
???? ???1 prestito: 0

Passo II:

0010 0011 -
0001 0110 =
-------------
???? ??01 prestito: 0

Passo III:

0010 0011 -
0001 0110 =
-------------
???? ?101 prestito: 1

Passo IV:

0010 0011 -
0001 0110 =
-------------
???? 1101 prestito: 1

Passo V:

0010 0011 -
0001 0110 =
-------------
???0 1101 prestito: 1

Passo VI:

0010 0011 -
0001 0110 =
-------------
??00 1101 prestito: 0

Passo VII:
0010 0011 -
0001 0110 =
-------------
0000 1101 prestito: 0

Risultato: 0000 1101 = 0D16 = 1310.

Soluzione n° 2
42 + 31 = 0010 1010 + 0001 1111

Svolgendo i calcoli, si trova:

0010 1010 +
0001 1111 =
-------------
0100 1001

Risultato: 0100 1001 = 4916 = 7310.

Soluzione n° 3
7 * 12 = 0000 0111 * 0000 1100

Svolgendo i calcoli, si trova:

00000111 *
00001100 =
-------------
00000000
00000000
00000111
00000111
-----------
0101 0100

Risultato: 0101 0100 = 5416 = 8410.

Soluzione n°4
23 / 7 = 0001 0111 / 0000 0111
Eseguendo i calcoli passo passo, si ha successivamente:

Passo I:

1011 1 / 111 = 1

resto 0100
(infatti 11 / 7 dà 1 con resto 4)

Passo II:
0100 1 / 111 = 1

resto 0010
(infatti 9 / 7 dà 1 con resto 2)

Risultato: 11

con resto di 2
(infatti 3 * 7 + 2 = 21 + 2 = 23).

Struttura di un elaboratore e sistema di elaborazione

Introduzione
Esiste oggi una vastissima gamma di calcolatori elettronici, che rispondono alle più svariate
esigenze :

• , memorizzare testo e gestirne la grafica, attraverso l'uso


word processing
di comandi e algoritmi;

• , gestire archivi informativi. Memorizzare dati, permetterne


basi di dati
il recupero veloce e l'estrazione di informazioni sui dati;

• , attraverso l'uso di reti di calcolatori, Internet.


accesso remoto
Trasmissione e recupero veloce dell'informazione;

• calcolo , risoluzione di problemi matematici;

• , rappresentare in termini di informazione una determinata


simulazioni
situazione ed elaborarla sulla base di regole che riproducono la
realtà (sia per scopo scientifico che per intrattenimento, ad
esempio i giochi).

Nonostante le differenze di dimensione, di costo e di utilizzo, tutti questi elaboratori


posseggono la stessa struttura interna e le stesse capacità logiche.
Ogni calcolatore è costituito da:

Hardware

(processore, memorie, dispositivi


periferici)
+ Software di base

Hardware = parte fisica Software = parte logica

L'hardware (hw) è l'insieme delle componenti fisiche che costituiscono l'architettura del calcolatore;
è composto logicamente da:

• memoria centrale

• unità centrale di elaborazione (CPU)

• unità di INPUT OUTPUT

• bus di sistema

• Il software (sw) è l'insieme di programmi ovvero sequenze di


istruzioni che indicano al calcolatore come manipolare i dati.

Il calcolatore è una macchina in grado di svolgere diverse funzioni.


Le funzioni, realizzate da uno o più componenti fisiche (hw) che interagiscono fra loro,
possono essere:

1. elaborazione dei dati;

2. memorizzazione dei dati;

3. trasferimento dei dati;

4. controllo delle precedenti funzioni.

Evoluzione del calcolatore


Nella storia dei calcolatori vengono comunemente identificate cinque diverse generazioni di
famiglie attraverso le quali le macchine si sono evolute.
Prima generazione (1938-1953): l'era delle valvole elettriche
Il funzionamento dei calcolatori appartenenti a questo periodo è basato sullo stato on-off di
relé elettromeccanici successivamente sostituiti dai tubi a vuoto (valvole). Il primo calcolatore
realizzato secondo questa tecnologia è l'ENIAC (1944). La caratteristica più importante, per
quanto concerne le macchine della prima generazione, è la presenza dell'EDVAC provvisto di
una memoria programmi e funzionante in logica binaria; esso ha portato alla nascita dei
linguaggi assemblativi. Successivamente si sono affermati l'UNIVAC (1953) e l'IBM 701.

Seconda generazione (1954-1963):l'era dei transistor


La tecnologia della generazione precedente viene sostituita dall'utilizzo dei transistor. Il primo
computer digitale transistorizzato, il TRADIC, compare nel 1954, contemporaneamente ai
circuiti stampati ed alle memorie a nuclei magnetici. Gran parte della prima metà del periodo
caratterizzante questa generazione è stato dedicato al perfezionamento del TRADIC.
Contemporanamente i linguaggi assembly iniziano a perdere il loro predominio in favore dei
linguaggi più evoluti qualiil FORTRAN, l'ALGOL ed il COBOL che si affermano sempre più.

Terza generazione (1963-1975): l'era dei circuiti integrati


È caratterizzata dall'affermarsi dei circuiti integrati delle famiglie SSI ed MSI che hanno
portato ad un graduale abbandono delle memorie a nuclei magnetici. Tutte le innovazioni di
questo decennio sono riscontrabili nei sistemi serie 360 dell'IBM : multiprogrammazione,
compatibilità tra le diverse macchine, possibilità di simulazione. Questa è anche la
generazione dei primi sistemi TIME-SHARING, delle memorie virtuali e dei primi processori
vettoriali.

Quarta generazione (1975-1985): L'era dei circuiti integrati VLSI


È caratterizzata da un utilizzo in larga scala della integrazione (LSI, Large Scale Integretion),
incorporando su un unico chip migliaia di di transistor, che consente di migliorare il rapporto
costi-prestazioni-dimensioni del sistema. È la generazione dei grandi MAINFRAMES ad alta
velocità e dei supercomputer con enorme capacità di memoria ed elevatissima velocità
nell'esecuzione di operazioni in virgola mobile. È anche il periodo dei microprocessori. Dagli
albori degli anni 80 si assiste ad una crescente commercializzazione di personal computers ed
all'avvio dell'informatizzazione di massa.

Generazione attuale: l'era dell'informatica distribuita


Gli attuali microcomputer single-chip sono una dimostrazione di come le funzioni di un
personal computer possono essere riprodotte su un singolo circuito integrato a 64 o 40 pin. Si
è arrivati al punto che è possibile ridurre le dimensioni dei calcolatori aumentandone la
velocità.
Si parla oggi di informatica distribuita riferendosi alla migrazione delle applicazioni dai grossi
mainframe a reti di PC. Uno schema architetturale di distribuzione del software, chiamato
client-server, prevede che le applicazioni siano strutturate in due moduli: un modulo client,
installato sui PC distribuiti in rete, che comprende la parte del programma che gestisce la
visualizzazione dei dati dall'utente; e un modulo server, installato su un computer centrale che
comprende quelle parti dell'applicazione che gestiscono le elaborazioni più intense e di uso
comune.
Architettura del calcolatore
Gli attuali calcolatori digitali consistono in sistemi che trasformano dati di ingresso (input) in
dati in uscita (output) sotto il controllo di una sequenza memorizzata di istruzioni
(programma)

L’architettura di un calcolatore deve possedere le seguenti proprietà:

1. Flessibilità : la medesima macchina può essere utilizzata per


compiti molto differenti, nessuno dei quali è predefinito al
momento della costruzione

2. Velocità di elaborazione dei dati

3. Grandi capacità di memorizzazione

4. Essere modulare

5. Essere scalabile

6. Avere componenti standard, economici, facili da installare

7. Applicazioni disponibili e con basso prezzo

L’architettura di riferimento è la macchina di Von Neumann :


• La CPU svolge le funzioni di elaborazione e controllo. Essa contiene
i dispositivi elettronici in grado di acquisire, interpretare ed
eseguire i programmi trasformando i dati. Ogni istruzione è
logicamente eseguita in sequenza, ma alcuni calcolatori sono in
grado di eseguire più istruzioni in una volta.

• La Memoria svolge la funzione di memorizzazione. La memoria


centrale contiene sia dati che istruzioni, è il sistema operativo che
carica questi dalla memoria di massa per l'esecuzione dei
programmi. La memoria centrale è suddivisa in celle o locazioni di
memoria, ognuna delle quali identificata da un indirizzo.

• Le unità di Input Output (I/O) permettono lo scambio d'informazione fra


l'elaboratore e l'esterno.

• Il Bus consente la comunicazione fra i vari componenti, in quanto


collega gli elementi funzionali dell'elaboratore, fornendo il
supporto fisico per la trasmissione dei dati tra i vari elementi.
Le MEMORIE
La macchina di Von Neumann si fonda sull'esecuzione di istruzioni il cui codice deve essere
contenuto in una memoria di accesso immediato.
I programmi in esecuzione e i dati sono contenuti nella memoria centrale.

Sia per l'elevato costo che ne limita la capacità, che per la sua natura volatile (non trattiene le
informazioni in assenza di alimentazione elettrica), alla memoria centrale viene affiancata la
memoria di massa, non volatile, magnetica o ottica, capace di mantenere nel tempo una grande
quantità di informazioni a costi più bassi rispetto la memoria centrale.

Per utilizzare i dati ed eseguire i programmi le informazioni vengono copiate dalla memoria di
massa a quella centrale e viceversa per la memorizzazione nel tempo.

Vi possono essere inoltre memorie di transito (buffer, cache) usate di solito per aumentare la velosità di
trasferimento tra mezzi con proprietà diverse.

Unità di grandezza per la memoria

• BIT: 0 o 1

• BYTE: 8 bit

• KILOBYTE (KBYTE, K): 1024 byte

• MEGABYTE (MEGA): 1024 KBYTE = 1.048.576 byte

• GIGABYTE (GIGA): 1024 MEGA = 1.048.576 K = 1.073.741.824


byte

Caratteristiche delle memorie:

• volatilità
se si spegne il PC la RAM viene cancellata, le memorie di massa
(floppy disk e hard disk) invece sono permanenti

• capacità (nota anche come spazio di indirizzamento)


numero di unità elementari (byte o parole) di informazione che
può essere immagazzinato in memoria;
valori tipici sono:

• 64, 128 Mb per la RAM

• 1.44Mb per floppy disk

• 4,8 Gb per l’hard disk


• tempo di accesso
tempo necessario per completare una richiesta di lettura o
scrittura

• velocità di trasferimento
dei dati sia in lettura che in scrittura

Tipologie di accesso alla memoria:

• sequenziale

• casuale (diretto)

• misto

• associativo (hash)

Tecnologie per realizzare memorie:

• memorie elettroniche (tipi : SRAM, DRAM, ROM, PROM


(Programmable ROM), EPROM (Erasable-Programmable ROM), Flash)

• memorie magnetiche

• memorie ottiche
La CPU
La CPU (Central Processing Unit) , detta anche processore è l’unità più importante del sistema di
elaborazione, che svolge le principali operazioni di calcolo e di controllo del sistema. In un
personal computer la CPU è collocata su un singolo chip e viene chiamata microprocessore.

Le componenti principali sono:

1. l' unità logico aritmetica (ALU, Arithmetic Logic Unit), che esegue le
operazioni elementari necessarie per l'esecuzione delle istruzioni,
ovvero operazioni aritmetiche e confronti sui dati della memoria
centrale o dei registri; essa è costituita da un rete logica
combinatoria programmabile capace di operare parallelamente su
un determinato numero di bit, e da un registro di scorrimento e da
un registro di flag (ST)che contiene alcune informazioni logiche
sul risultato delle operazioni. Per la memorizzazione dei dati in
ingresso e dei risultati l'ALU utilizza alcuni registri operativi che
permettono di risolvere il problema del numero di indirizzi che
devono essere contenuti in una istruzione ( uno di questi è
l'Accumulatore).

2. l'unità di controllo (CU, Control Unit) che controlla e coordrina


l'attività della CPU. E' responsabile del trasferimento e della
decodifica delle istruzioni dalla memoria centrale ai registri della
CPU;

3. il clock è l'orologio interno che scandisce la durata di un ciclo di


esecuzione, permettendo il sincronismo delle operazioni. La
frequenza del clock si misura in Mhz e indica il numero di
operazioni elementari eseguite in un secondo: essa fornisce
un'idea della velocità di elaborazione del computer;

4. i registri. Ogni registro è una locazione utilizzata per memorizzare


all'interno della CPU:

5. dati prelevati dalla memoria e su cui la CPU deve lavorare;

6. istruzioni prelevate dalla memoria e che la CPU deve eseguire;

7. indirizzi di celle di memoria all'intrno delle quali ci sono dati o


istruzioni da prelevare.

I registri della CPU sono:

8. PC : Program Counter, che contiene l'indirizzo in memoria della


prossima istruzione che si deve prelevare ed eseguire. Poichè le
istruzioni sono memorizzate in sequenza, si incrementa ogni
istruzione;
9. : Registro istruzione corrente, contiene l'istruzione che si
RIC (o IR)
deve eseguire in quel momento; inizialmente deve essere caricato
con la prima istruzione;

10.RIM : Registro indirizzi di memoria, contenente gli indirizzi in


memoria dei dati su cui le istruzioni hanno bisogno di operare, e
che devono perciò essere prelevati dalla memoria a
quell'indirizzo;

11.ST : Status Register, è costituito da un insieme di flag ognuno dei


quali serve per mantenere una indicazione concisa sull'esito
dell'ultima operazione eseguita, infatti tale registro riporta sui
suoi bit indicazioni sul risultato dell'operazione(riporto, overflow,
ecc.).

12.ACC : Accumulatore

13.RDM : Registro dati di memoria

(considerare RI=RIC e RS=ST)

La CPU non lavora direttamente sulla memoria centrale, ma


sposta temporaneamente nei suoi registri ciò che gli interessa in
un certo momento.

Unità di Input-Output
Monitor
periferica di
Output

Stampante
periferica di
Output
Tastiera
periferica di Input

Mouse
periferica di Input

Modem
periferica di
Input/Output

Floppy disk
periferica di
Input/Output

Hard disk
periferica di
Input/Output
L’I/O si realizza tramite le periferiche sopra elencate, ognuna delle quali comunica con il PC
tramite un’opportuna interfaccia di comunicazione.
L’interfaccia può essere un semplice connettore on-board (tastiera) oppure una scheda da
inserire in uno slot (PCI, ISA, AGP) con una o più porte per la connessione della periferica
stessa.
Le modalità di trasmissione possono essere:

• seriale (RS-232)

• parallela (Centronics)

Sincronizzazione:
• polling: ciclo della CPU sulle periferiche, semplice ma impegna la
CPU e alcuni device non possono aspettare;

• interrupt: richiesta della periferica alla CPU, più complessa


(subroutine per la gestione) e richiede comunque la CPU;

• DMA : quando ci sono grossi trasferimenti di dati, si usa un DMA


controller, che sgrava la CPU del lavoro. La CPU comunica
soltanto con il DMA, non con le periferiche. I due si contendono
ancora il bus, quindi;

• Coprocessori per I/O e bus dedicati.


BUS
Il Bus è una linea di comunicazione ad n bit che connette tutti i componenti di un calcolatore,
è quindi un collegamento tra dispositivi.

Serve per il trasferimento di dati, istruzioni, indirizzi e segnali di controllo.


Per esempio il bus di sistema di un PC è costituito da 50 a 100 cavi di rami paralleli.
Vi possono essere bus dedicati a scopi speciali :

• collegamento a coprocessori;

• memorie locali;

• dispositivi di I/O;

• ecc.

I progettisti sono liberi di realizzare bus locali, ma per rendere possibile il collegamento con
dispositivi realizzati da terze parti devono rispettare opportune regole (logiche, meccaniche,
elettriche) dette protocollo del bus.

Essendo un risorsa condivisa, occorre introdurre l’arbitraggio.


Una delle soluzioni possibili è quella di avere:

• un componente master (generalmente la CPU)

• il resto slave (devono chiedere al master l’uso del bus)

Caratteristiche del Bus:

• semplicità (unica linea, l’alternativa sarebbero link punto-punto)

• espandibilità (facile aggiungere nuovi componenti)

• standardizzabilità (unico protocollo di comunicazione)

• lentezza (accodamento richieste)

• limite di capacità (non si possono connettere infiniti componenti)

• sovraccarico CPU

Tipologie di Bus:

• Il numero di linee da cui è composto questo bus è 16. È


Address bus.
unidirezionale in uscita perché è l'unità centrale a generare ed
inviare indirizzi alla memoria o alle porte di I/O.
• È un bus bidirezionale ad 8 bit preposto al trasporto dei
Data bus.
dati. Le otto linee di questo bus permettono al dato diviaggiare in
un senso o nell'altro, anche se, ovviamente, non è possibile che
singoli bit viaggino nei due sensi opposti contemporaneamente.

• Control bus. È costituito da 13 linee di cui 8 in uscita e 5 in ingresso.

Il multiplexing dà la possibilità di mettere tutto su un unico bus


fisico e alternare i vari tipi di bus.

Principi di funzionamento del calcolatore


La CPU opera secondo il ciclo fecth-decode-execute.

Esecuzione di un programma memorizzato:

• Il programma risiede in memoria sotto forma di sequenza di


istruzioni;

• L'unità di elaborazione, la CPU (Central Processing Unit) chiede


la lettura della prossima istruzione da eseguire, e manda sul bus
degli indirizzi la locazione di memoria in cui si trova tale
istruzione, la memoria risponde dando alla CPU l'istruzione,
mettendola sul bus dei dati (FETCH) ; la CPU la mette al suo
interno ( nei registri), e la decodifica (DECODE), ossia si rende
conto di cosa deve fare per eseguire quell’istruzione; infine, la
CPU effettua tutte le microoperazioni che realizzano
quell’istruzione, eseguendole(EXECUTE).

NOTE:

• L'unità di elaborazione è una macchina sequenziale sincrona

• Durante l'esecuzione del programma, la CPU scambia


informazioni con la memoria principale e le interfacce di ingresso
uscita attraverso il bus

• L'accesso alle informazioni (istruzioni e operandi) avviene sempre


mediante il rispettivo indirizzo definito in uno spazio di
indirizzamento (es. memoria o input/output)

• istruzioni, operandi e indirizzi sono codificati con codici binari

• il meccanismo che gestisce il trasferimento delle informazioni sul


bus è il ciclo di bus

• Ad ogni ciclo di bus sono associati un indirizzo, un comando (es.


read o write) e l'informazione trasferita. Chi genera l'indirizzo e il
comando in un particolare ciclo di bus è detto "bus master" in
quel ciclo; chi vede gli indirizzi e i comandi come input viene detto
"slave"; master e slave si chiamano agenti (bus agent)

• la CPU è bus master quando accede alle sue istruzioni e ai suoi


operandi attraverso il bus

• Un bus può essere condiviso da più bus master che lo pilotano a


divisione di tempo (es. sistemi multiprocessore o sistemi in cui il
trasferimento di dati tra la memoria e le interfacce di
ingresso/uscita è eseguito da un apposito "bus master" detto DMA
Controller)
Collegamento di calcolatori in rete

Cenni di reti di calcolatori


Attraverso le reti di calcolatori si collegano in rete più calcolatori per scambiare tra loro le
informazioni, per far ciò è possibile usare diverse tecnologie come il doppino telefonico, fibre
ottiche, via satellite ecc.

Tecnologia di trasmissione
Broadcast
I messaggi vengono inviati da una macchina a tutte le altre (i messaggi contengono l'indirizzo
del destinatario, che considera solo quelli diretti a lui).
Point-to-point
Un messaggio andando dal mittente al destinatario visita delle macchine intermedie (esistono
possibili diversi cammini tra due nodi).

Tipi di reti

• reti locali (LAN)


stanza (10 m), edificio (100 m), campus (1 km)

• reti metropolitane (MAN)


città (10 km)

• reti geografiche (WAN)


nazione (100 km), continente (1000 km)
Livelli, protocolli, interfacce

Architetture di reti
INTERNET

La rete di comunicazione che collega tra loro reti di computer e sistemi informatici di diverso
tipo, viene detta Internet.

è, quindi, un sistema di reti di computer distribuito su tutto il pianeta, dal quale ogni
Internet
utente può ottenere informazioni da altri computer.

In generale la rete Internet può essere vista come:

• Risorsa informativa
Internet è strumento di comunicazione. Come la rete telefonica,
Internet nasce per permettere la comunicazione e lo scambio di
informazioni che non sono solo suoni, ma anche testi, software,
immagini, suoni digitalizzati, ecc.
Internet è dunque simile a una rete telefonica nata per far
comunicare fra loro dei computer, e le persone che stanno dietro ai
calcolatori. Questo è l'elemento principale per capire che Internet
è sia una risorsa informativa che un luogo di interazione culturale,
sociale, economica.

• Villaggio globale
Internet è un luogo di innumerevoli interazioni sociali.

• Vi è lo scambio di posta elettronica ( e-mail ). La velocità di


trasferimento dell'informazione, la semplicità della preparazione e
dell'invio del messaggio rendono la posta elettronica uno
strumento utilissimo.

• I newsgroup , immense bacheche elettroniche dedicate ad uno


specifico argomento. Chiunque all'interno della rete può accedervi
per leggere i messaggi lasciati dagli altri e per inserirne a sua
volta.

• Le chat, in cui i partecipanti sono collegati contemporaneamente,


avendo un' interazione in tempo reale: quanto viene digitato da un
utente compare sul video degli altri, che possono a loro rispondere
con altrettanta immediatezza.

• Infine alcuni nuovi strumenti danno la possibilità di utilizzare la


rete, in tempo reale e a prezzo più basso di quello tradizionale, per
le telefonate via Internet.

• Mercato globale
Con Internet si è venuta a creare un'interconnessione fra i mercati
finanziari di tutto il mondo. Ma non solo fra questi, perchè adesso
chiunque ha la possibilità di acquistare tutto ciò che si vuole in
rete. Infatti si hanno a disposizione cataloghi con una vastità di
articoli che nessun grande magazzino sarebbe in grado di offrire.
Come funziona Internet
Internet viene definita rete di reti perchè è una interconnessione fra reti.

Ogni singola rete componente puo' essere una:

• LAN - Local Area Network - rete locale

• WAN - Wide Area Network - rete geografica

• MAN Metropolitan Area Network - rete metropolitana, intermedia

Tutte le reti considerate sono a commutazione di pacchetto .

Si distinguono due tipi di nodi di rete:

• a connessione singola, agganciati ad una sola rete componente

• a connessione multipla, agganciati a piu' reti componenti

Nei nodi a connessione multipla esiste una scheda hardware di interfaccia di rete per ogni rete
componente collegata. Le reti componenti si dicono contigue.

Viene definito router un nodo di rete a connessione multipla, che compie lo smistamento di
pacchetti tra le sue reti contigue.

Lo smistamento di pacchetti tra un nodo sorgente ed uno destinazione e' uno dei problemi
principali di una intranet: per risolverlo viene adoperata una serie di protocolli di rete
cooperanti.

Se l'internet in questione ricopre l'intera superficie del globo, con decine di milioni di nodi
collegati, allora viene detta, per antonomasia, Internet.

La serie di protocolli alla base di Internet viene anche detta la serie di protocolli TCP/IP.

• TCP/IP suddivide i dati in pacchetti e li invia su Internet.

• Quando le informazioni giungono a destinazione, TCP/IP


controlla che tutti i pacchetti siano integri.

Vediamo in dettaglio questo protocollo:

• , suddivide il messaggio in pacchetti numerati e


TCP (Transport Control Protocol)
invia le diverse parti corredate da un indirizzo IP; all'arrivo il
messaggio verrà ricomposto garantendone correttezza e integrità.
• , determina il percorso migliore tra le diverse linee e
IP (Internet Protocol)
provvede a consegnare il pacchetto a destinazione.
Indirizzo Internet

L'Indirizzo Internet identifica una singola interfaccia fisica con cui un nodo di rete si aggancia
a Internet, e deve essere univoco. Esso e' composto da due registri di 32 bit:

• il registro di indirizzo: numero binario effettivo univoco che


identifica il nodo; e' diviso in due parti:

• il prefisso di rete. La lunghezza del prefisso non e' determinata a


priori. Non possono esservi due reti in Internet con lo stasso
prefisso.

• l'indirizzo di host all'interno della rete. E' la parte rimanente


dell'indirizzo IP: non vi possono essere due nodi della stessa rete
con lo stesso indirizzo di host.

• il registro di maschera di rete. E' composto da una parte iniziale di


bit settati a 1, di lunghezza uguale al prefisso di rete, e la parte
rimanente con bit settati a 0, corrispondente all'indirizzo di host.
Questo registro non e' indispensabile, e veniva anche detto
precedentemente maschera di sottorete. Serve effettivamente in
modo ausiliario per determinare quale sia il prefisso di rete nel
registro di indirizzo.

Tutte le interfacce di rete dei vari nodi agganciate ad una stessa rete logica devono avere:

• la stessa maschera di rete

• lo stesso prefisso di rete

• differenti indirizzi di host

Per convenienza umana i registri di indirizzo e di maschera vengono espressi nella cosiddetta
notazione punto: quattro bytes decimali separati da un punto.

E' conveniente compiere tutti i calcoli necessari in formato binario e solo come ultima
operazione esprimere gli indirizzi in notazione punto.

Indirizzamento simbolico: Domain Name Service (DNS)


Esempio: 151.97.6.104 <-->saturno.iit.unict.it

La prima parte indica il nome del computer, la rimanente è detta dominio e individua l'ente,
l'azienda o l'organizzazione a cui il computer è connesso.

In saturno.iit.unict.it
it rappresenta il dominio di primo livello

Sigle delle nazioni:

• USA (caso speciale)

• EDU: università ed enti di ricerca

• COM: organizzazioni commerciali

• GOV: enti governativi

• NET: organizzazione di supporto e gestione della rete

• MIL: enti militari

• ORG: organizzazioni ed enti non rientranti nelle categorie


precedenti

Connessione a Internet

Tipologia delle connessioni a Internet

• Collegamenti diretti con linee dedicate:

• inserimento di un computer in una sottorete esistente;

• creazione di una nuova sottorete.

• Collegamenti dialup con linee telefoniche normali o ISDN

Per collegarsi ad Internet da casa ci vuole:

• un telefono (o meglio una linea telefonica)

• un computer

• un modem
• i programmi che controllano il modem e che permettono di
compiere le operazioni desiderate in rete un fornitore di
connettività ( provider ).

La linea telefonica può essere:

• RTC (Rete Telefonica Commutata) che presenta bassi costi e basse


velocità.

• ISDN (Integrated Services Digital Network) che presenta maggiori


velocità, la possibilità di più chiamate sulla stessa linea; ma anche
un canone più caro.

Il modem (modulatore/demodulatore):

è un apparecchio che codifica e trasforma dati binari in impulsi


che possono essere trasmessi attraverso una linea telefonica .
I parametri per sceglierlo sono: tipologia (interno, esterno,
PCMCIA) e velocità (33600 bps, 56000 bps ecc.), più è alta e più le
comunicazioni sono veloci.

Il provider :

rappresenta il sistema telematico che affitta parte della sua


connettività ad Internet al privato cittadino ed è in genere
chiamato Internet Service Provider o ISP.
Sono molti i fattori che determinano la qualità del provider:

• il numero ed il tipo di modem messi a disposizione dei propri


utenti;

• la portata e la differenziazione delle linee che lo connettono ad


Internet;

• la possibilità di connettersi al costo di una telefonata urbana;

• ecc.
Accesso ai servizi su Internet

Architettura Client-Server

L'architettura client-server è basata sul collegamento e l'utilizzo di risorse informatiche di vario tipo tra
loro interconnesse.
Il funzionamento è basato su due principi fondamentali: le risorse sono distribuite e condivise
da più utenti anche lontani fra loro.

All'interno di questa architettura il server mette a disposizione le proprie risorse sia hardware
che software, e i client possono utilizzare tali risorse messe a disposizione.

Il client necessita di un programma di interfaccia con l’utente che si occupi di richiedere i dati.
Il server necessita invece di un programma che si occupi del mantenimento, del reperimento e
dell’invio dei dati al client che li ha richiesti.

Affinché l’interazione tra Client e Server possa avvenire è necessario che entrambi utilizzino
un linguaggio comune, ovvero un protocollo di comunicazione.

I più comuni sono:

• Simple Mail Transfer Protocol (SMTP)

• File Transfer Protocol (FTP)


• Hypher-Text Transfer Protocol (HTTP)

La posta elettronica o e-mail

L’indirizzo Internet di ogni computer collegato in rete è composto da 4 gruppi di numeri


separati da un punto (ad esempio 200.132.7.31 ), o per semplicità da sigle o parole di poche lettere.

Inoltre ogni singolo utente viene identificato attraverso un username o nome simbolico che si trova
all'inizio dell'indirizzo simbolico del computer, e corrisponde ad una sorta di casella postale
ospitata dal computer dell’ISP.

Esempio:

GMangioni@k200.iit.unict.it

• La parte di indirizzo alla sinistra del simbolo @ (detto 'chiocciola'


o, con riferimento al suo significato all'interno di un indirizzo
Internet, 'at') identifica l'utente in maniera univoca all'interno del
sistema informatico che lo ospita (host system); spesso si tratterà
del nostro cognome, o di un codice, o di un nomignolo che ci siamo
scelti.

• La parte di indirizzo a destra del simbolo @ identifica invece in


maniera univoca, all'interno dell'intera rete Internet, il particolare
sistema informatico presso il quale l'utente è ospitato, e
corrisponde all'indirizzo simbolico dell'host.
L'ipertesto globale: World Wide Web

Il World Wide Web (WWW) è stata l'ultima funzionalità di Internet ad essere sviluppata, essa rappresenta
letteralmente una ragnatela estesa sul mondo.
Per organizzare le informazioni WWW utilizza la tecnica degli ipertesti, realizzando in tal modo
un collegamento non sequenziale tra vari documenti, che risiedono anche su macchine
diverse.

La storia di World Wide Web inizia intorno al 1990 quando Tim Berners Lee, ricercatore
presso il CERN di Ginevra, realizza un sistema di distribuzione dei documenti sulla rete
destinato alla comunità dei fisici delle alte energie.

Nel 1993 nell'Università dell'Illinois viene realizzata la prima interfaccia grafica


multipiattaforma per l'accesso ai documenti pubblicati su World Wide Web: Mosaic.
La semplicità di uso di Mosaic e le caratteristiche innovative dell'architettura informativa del
Web, nel giro di pochissimi mesi, hanno conquistato tutti gli utenti della rete, dando inizio ad
un processo di espansione tecnologica senza pari nel passato.

Le caratteristiche che hanno fatto di World Wide Web una vera e propria rivoluzione nel
panorama degli strumenti di comunicazione possono essere riassunte nei seguenti punti:

• la sua diffusione planetaria;

• la facilità di utilizzazione delle interfacce;

• la sua organizzazione ipertestuale;

• la possibilità di trasmettere/ricevere informazioni multimediali;

• le semplicità di gestione per i fornitori di informazione.

I vari archivi e documenti, registrati nei vari computer connessi in rete e resi disponibili agli
utenti della rete, costituiscono i siti Internet, dai quali è possibile consultare e prelevare
informazioni attraverso le pagine.
All'interno delle pagine ci sono delle parole selezionabili con il mouse che collegano ad altre
pagine attraverso i link.
Con tale tecnica WWW permette ad ogni utente di accedere a milioni di pagine di dati e
informazioni. La modalità con cui si possono consultare tali informazioni ipertestuali è detta
navigazione.

Ogni utente può visualizzare pagine Web sulla propria macchina usando un programma detto
browser.
Sistema di elaborazione: software di base
Introduzione

Le funzioni di un calcolatore, come già visto, sono:

• elaborazione dei dati;

• memorizzazione dei dati;

• trasferimento dei dati;

• controllo delle funzioni precedenti.

Le sue caratteristiche principali durante il normale funzionamento sono:

• flessibilità nel calcolo;

• modularità nell’architettura;

• scalabilità dei componenti;

• standardizzazione;

• abbattimento dei costi.

Tutte le applicazioni del calcolatore sono possibili attraverso l'interazione di hardware e


software.
E' grazie al software che si possono sviluppare, elaborare e controllare i compiti per cui
l'elaboratore è utilizzato.

In particolare l'attività con cui si predispone l'elaboratore per poter risolvere certi problemi o
elaborare l'informazione secondo certi criteri è la programmazione.
Nell'evoluzione del software sono man mano nati linguaggi di programmazione che si sono
svincolati dall'hardware della macchina.
E' chiaro che l'insieme di istruzioni di un programma deve essere dato in un linguaggio
comprensibile all'elaboratore, ed ovviamente non può essere utilizzato direttamente il
linguaggio naturale dell'uomo, ma linguaggi di programmzione adatti.

A qualunque livello di astrazione si lavori è necessaria la presenza di una macchina astratta capace di
eseguire le istruzioni del linguaggio cioè:

• interpretare il linguaggio, ovvero

• eseguire le azioni elementari;

• determinare l’istruzione successiva.


L’interprete di cui stiamo parlando può essere sia hardware che software.

Sistema operativo

Gli attuali Sistemi Operativi (SO) impiegati possono essere visti come una collezione di
programmi interagenti che operano sull’hardware per fornire agli utenti un insieme di
funzioni ad un elevato livello di astrazione.

I vantaggi sono:

• definire modalità di interfaccia standardizzate verso il calcolatore;

• sviluppare i programmi in maniera più semplice, modulare e


indipendente dallo specifico calcolatore;

• permettere l’aggiornamento del software di base in maniera


trasparente.

I sistemi operativi sono tra i programmi più complessi e difficili da realizzare, pertanto sono
realizzati a livelli, dove ogni livello usa le funzionalità del livello precedente e fornisce
funzionalità a quello successivo (struttura a cipolla).

I livelli usuali di un sistema operativo sono:

• hardware

• Nucleo (gestore dei processi)

• Gestore della memoria

• Gestore delle periferiche

• Gestore dei file (file system)

• Interprete dei comandi (shell)

• Applicazioni

Nucleo
Questo strato colloquia direttamente con l’hardware. Esegue i programmi e risponde agli
eventi esterni generati dalle unità periferiche.
Maschera ad un utente la presenza di altri e gestisce i processi contemporaneamente attivi.

Gestore della memoria


Esso ha il compito di gestire la memoria in modo trasparente ed efficiente.
Consente ad ogni programma di lavorare in un proprio spazio di indirizzamento virtuale.
Deve proteggere i dati e le istruzioni dei programmi e mascherare la collocazione fisica.
Inoltre deve permettere la sovrapposizione degli spazi di memoria associati ai vari
programmi.

Gestore delle periferiche


Permette all’utente di operare mediante periferiche astratte.
Maschera le caratteristiche fisiche e le operazioni di I/O all’utente, e infine risolve i conflitti
tra i vari utenti.

Gestore del file system


Si occupa di organizzare le informazioni, che vengono strutturate in contenitori logici ( file )
identificati mediante un nome logico (filename).
Solitamente il nome del file è composto da due parti: nome ed estensione

Il file system offre le procedure ad alto livello per operare sui file presenti sulle memorie stabili.
Esso permette di:

• recuperare le informazioni precedentemente memorizzate;


• eliminare le informazioni obsolete;
• modificare i dati preesistenti;
• gestire il backup;

Il file system su un disco è organizzato ad albero, in cui ogni nodo è detto directory, ed ogni
file è univocamente individuato dal cammino (path) che parte dalla radice e lo raggiunge.

Ogni sistema operativo offre dei comandi per operare sul file system, i più comuni sono quelli
per:

• creare e distruggere file e directory


• fare copie di file e directory
• muovere file e directory attraverso l'albero
• scegliere la directory corrente (cioè la posizione nell'albero in cui si sta operando
correntemente)
• visualizzare una directory
• visualizzare il contenuto di un file

Interprete dei comandi


L’interprete dei comandi e i programmi di utilità sono moduli direttamente visibili all’utente.
L’interprete dei comandi ha la funzione di interpretare i comandi che arrivano dalle
periferiche
Le operazioni svolte sono:
• lettura della memoria di massa del programma da eseguire;
• allocazione della memoria centrale;
• caricamento nella memoria del programma e dei relativi dati iniziali;
• creazione ed attivazione del processo.
Gestione dei processi

Un sistema operativo per poter gestire contemporaneamente più programmi, ognuno dei quali
può essere eseguito, bloccato e ripreso al punto in cui era stato sospeso precedentemente, deve
disporre di una struttura che possegga istante per istante lo stato di ogni programma in
esecuzione. Tale struttura astratta è il processo.

Processo e programma

• Un processo è un programma in esecuzione sul calcolatore.

• Un programma è un oggetto statico (una sequenza di istruzioni).

• Un processo invece è dinamico, cioè è dotato di uno stato interno


che cambia nel tempo.

• Lo stesso programma può essere associato a più processi distinti.

Stati del processo

• Executing o Running (in esecuzione):


il processore è a disposizione per l’esecuzione del processo.
In un calcolatore mono-processore ad ogni istante un solo processo
può trovarsi in questo stato.

• Ready (pronto):
in grado di essere eseguito ed in attesa del processore dopo il suo
rilascio da parte di altri processi.

• Waiting (in attesa):


non in grado di essere eseguito perché in attesa di un evento
esterno.

La multiprogrammazione

I sistemi operativi che permettono di eseguire contemporaneamente più programmi sono detti
multitasking.
In sistemi con un solo processore l'esecuzione contemporanea dei processi è virtuale, in quanto
la CPU esegue un'istruzione per volta.
Con il termine multiprogrammazione si intende che più programmi possono risiedere
contemporaneamente in memoria centrale.

Il multitasking è nato per poter rendere minimo il tempo di inattività della CPU durante
operazioni in cui non viene utilizzata.
La politica che si utilizza per poter tenere più programmi in esecuzione senza sprecare tempo
di CPU e penalizzare i processi in coda (facendoli attendere troppo a lungo) è la ripartizione
del tempo, ovvero il time sharing.
In tal modo viene fatta un'equa ripartizione del tempo di CPU fra tutti i processi attivi,
ognuno dei quali crede di operare sul calcolatore come se fosse a lui dedicato.
Tutto ciò è organizzato dal sistema operativo attraverso la gestione di una coda di processi
ciascuni di questi ha un quanto di tempo a propria disposizione.

Lo scheduler
Lo scheduler è quella parte del sistema operativo che si occupa di gestire i processi. Esso deve:

• essere efficiente;

• minimizzare il tempo di risposta;

• minimizzare il tempo d’attesa dei processi batch;

• massimizzare il throughput (numero di processi per ora);

• garantire che ogni processo abbia la sua giusta parte di tempo di


CPU.

I principali algoritmi di scheduling sono:

1. First Come First Served

2. Algoritmo Round Robin

3. Priorità

4. Code multiple

5. Il più breve prima (shortest job first)

Problemi legati alla concorrenza

Starvation:
avviene quando più processi richiedono una risorsa ma solo alcuni riescono ad ottenerla.
Deadlock:
avviene quando un gruppo di processi rimane permanentemente bloccato in attesa di una
risorsa.

Cooperazione fra processi:


memoria condivisa, scambio di messaggi
Gestione della memoria

Gli aspetti fondamentali da considerare nella gestione della memoria sono:

• Rilocabilità del codice

• Swapping

• Paginazione

• Memoria virtuale

• Segmentazione

Riguardo le strategie di gestione è necessario:

• consentire il caricamento del programma a partire da un


qualunque indirizzo di memoria;

• tenere in memoria solo una porzione dei dati e dei programmi;

• condividere insiemi di istruzioni da parte di processi facenti parte


di uno stesso programma.

Il gestore della memoria garantisce ai processi uno spazio di indirizzamento virtuale che può
essere superiore alle dimensioni della memoria fisica.
Rilocabilità

• Statica: il calcolo degli indirizzi è effettuata dal linker a cui il


valore di spiazzamento è fornito dal programmatore.

• Dinamica: il calcolo viene effettuato nel momento stesso in cui


necessità sapere l’indirizzo.
Questa tecnica richiede quindi che i programmi siano caricati in
memoria con gli indirizzi calcolati a partire dal valore 0, inoltre
deve essere presente un registro base che fornisca lo spiazzamento.

• Perché sia possibile creare del codice rilocabile è necessaria la


presenza di opportuni registri hardware.

• Per permettere una maggiore efficienza spesso il programma viene


diviso nella sezione del codice e in quella dei dati che vengono
copiati in memoria in aree separate.

Swapping

Nei sistemi multiprogrammati di uso comune la memoria non è sufficiente a caricare tutti i
programmi. Per tale motivo si utilizza lo swapping:

• il sistema operativo è abilitato a trasferire il contenuto di un’area


di memoria centrale in memoria di massa (area di swap).

• Generalmente vengono trasferiti i processi in stato di wait.

• Il sistema mette in stato di run solo i processi presenti in memoria.

Paginazione

La memoria centrale viene divisa in sezioni di dimensione fissa dette pagine logiche.
Questa tecnica ha i seguenti vantaggi:

• alloca la memoria ad un processo utilizzando zone di memoria non


contigue;

• permette di estendere la memoria allocata ad ogni processo.

Memoria virtuale
Questa tecnica è implementata mediante la segmentazione e la paginazione (possono essere
anche abbinate).
Vengono caricate in memoria principale solo delle porzioni limitate, ovvero il segmento di
programma che deve essere eseguito attualmente. Queste porzioni richiedono meno spazio di
quanto occorra ad un intero programma; in tal modo è possibile caricare nella stessa quantità
di memoria un maggior numero di programmi.
Quando viene indirizzata una pagina che non è in memoria (paging fault) il sistema operativo
si occupa di reperirla in memoria di massa e renderla disponibile.
Con la memoria virtuale è necessario comunque risolvere alcuni problemi connessi con la
scelta delle pagine da scaricare.

La segmentazione

Quando i programmi condividono elementi, come nel caso di processi che sono istanze dello
stesso programma, è vantaggioso non creare copie dello stesso codice.
Allora il programma viene diviso in segmenti:

• codice: contiene solo le istruzioni

• dati: contiene la parte allocata sia staticamente che


dinamicamente

• stack (pila): contiene i dati necessari alla gestione delle chiamate


di procedura
Dal linguaggio macchina a quelli ad alto livello

Ogni processore tratta solo segnali digitali sottoforma di valori binari, ogni istruzione che può
essere data al processore è codificata con valori binari.
L'insieme di tali valori che codificano le istruzioni di un processore costituiscono il linguaggio
macchina .

Linguaggio Macchina:

• è il linguaggio interpretabile direttamente dall'unità di controllo di un calcolatore, quindi


non necessita di nessuna traduzione;

• è rappresentato in codice binario ed è strettamente legato alla struttura logico-circuitale del


calcolatore, e alle operazioni elementari che questo sa eseguire;

• ogni linguaggio macchina è caratteristico e diverso per ciascun processore o famiglia di


processori;

• è costituito dall'insieme di valori che codificano tutte le possibili istruzioni di un processore.

• le istruzioni si dividono in due parti:

• codice operativo, che specifica l'operazione da compiere;

• uno o più operandi, che individuano le celle di memoria a cui si


riferiscono le operazioni.

Linguaggio Assembler:

• è una versione simbolica del codice macchina nel quale le operazioni sono indicate con nomi,
invece che con sequenze di numeri binari, e gli indirizzi di memoria e i parametri vengono
indicati con simboli;

• richiede di conoscere dettagliatamente le caratteristiche della macchina (registri, dimensioni


dati, set di istruzioni);

• i programmi sono scritti in file di testo e vengono tradotti in linguaggio macchina dagli
assemblatori;

• semplici algoritmi richiedono l'uso di molte istruzioni;


• per ciascuna architettura di processore è previsto uno specifico linguaggio assembler, in
quanto esiste, in linea di massima, una corrispondenza biunivoca tra istruzioni macchina
binarie e istruzioni assembler.

Linguaggi di Alto Livello:

• il programmatore può astrarre dai dettagli legati all’architettura e può esprimere i propri
algoritmi in modo simbolico;

• i linguaggi di alto livello sono indipendenti dalla macchina fisica (astrazione);

• vi sono linguaggi ad alto livello detti imperativi o orientati all'assegnamento che cercano di
rendere la programmazione semplice e veloce, e si fondano sull'impostazione del modello
architetturale classico (modello Von Neumann).
La caratteristica principale di questi linguaggi (Fortran, Cobol, C, Basic) è che le istruzioni
sono essenzialmente assegnamenti da dare a locazioni di memoria o registri;

• il primo linguaggio è stato il Fortran (FORmula TRANslator), col quale si è iniziata ad utilizzare
una notazione più vicina a quella usata da tecnici e matematici, rendendo più semplice lo
sviluppo dei programmi;

• il Pascal , invece è il primo ad incorporare in modo coerente i concetti di programmazione


strutturata, permettendo l'agevole uso di strutture dati come matrici e vettori;

• il linguaggio C è di uso generale ed è caratterizzato da estrema sinteticità e uso di strutture


dati avanzate (è trattato ampiamente in ...mettere l'href...).

Il modo di eseguire i programmi scritti nei linguaggi simbolici (ad alto livello) segue
principalmente due vie, quella degli interpreti e compilatori.
Compilatori e interpreti

I traduttori sono quei programmi che permettono di effettuare la traduzione dal codice sorgente
(non eseguibile dalla CPU) al linguaggio macchina.
Essi sono:

• gli assemblatori (che traducono ogni istruzione dal linguaggio


assembler in linguaggio macchina);

• gli interpreti;

• i compilatori.

L'interprete di un linguaggio è un programma che

• legge una alla volta le istruzioni di un programma sorgente;

• verifica la correttezza sintattica della istruzione, sulla base della


sintassi del linguaggio;

• in caso positivo (assenza di errori), sulla base della semantica del


linguaggio, la traduce nella corrispondente sequenza di istruzioni
in linguaggio macchina e

• la esegue.

Il compilatore di un linguaggio è un programma che

• verifica la correttezza sintattica del programma sorgente, sulla


base della sintassi del linguaggio;

• traduce il programma sorgente nel programma oggetto (sulla base della


semantica del linguaggio), il quale potrà successivamente essere
eseguito.

Quindi per eseguire un programma scritto con un linguaggio ad alto livello occorre tradurlo
nel linguaggio macchina dello specifico processore che si sta usando.

Vediamo adesso più in dettaglio le due modalità possibili:

• compilazione (con linguaggi come C,C++, FORTRAN, Pascal, ecc.)

• interpretazione ( con linguaggi come Basic, Perl, Java, ecc.)


I compilatori traducono un intero programma dal linguaggio L al linguaggio macchina della
macchina prescelta, considerando che:

• traduzione e esecuzione procedono separatamente;

• al termine della compilazione è disponibile la versione tradotta del


programma;

• la versione tradotta è però specifica di quella macchina;

• per eseguire il programma basta avere disponibile la versione


tradotta (non è necessario ricompilare).

Gli interpreti invece traducono e immediatamente eseguono il programma istruzione per istruzione,
infatti:

• traduzione ed esecuzione procedono insieme;

• al termine non vi è alcuna versione tradotta del programma


originale;

• se si vuole rieseguire il programma occorre anche ritradurlo.


Rappresentazione degli algoritmi
Introduzione

Risoluzione di un problema

Dato un problema relativo all'elaborazione dell'informazione, è necessario individuare un


metodo che trasformi i dati iniziali in una soluzione del problema.

In informatica risolvere un problema significa ricercare ed esprimere un elenco di istruzioni


che interpretate da un esecutore conducano da deteminate informazioni iniziali ad altre
informazioni finali soddisfacenti un criterio di verifica.

Un problema è decidibile quando è possibile trovare un algoritmo risolutivo.


Alcuni problemi decidibili:

• Dati n numeri, calcolarne la somma.

• Dato un elenco telefonico ed un nominativo, trovare il numero


telefonico.

• Dati le specifiche, progettare il ponte di Messina.

Alcuni problemi non decidibili:

• Dato il cambio attuale, determinare il cambio euro/dollaro di


domani.

• Decidere se la funzione f(x) è costante.

Esistono alcuni problemi decidibili che ammettono soluzioni non effettive, cioè il cui costo
temporale o spaziale non è sostenibile in pratica.

• Efficienza temporale, cioè la capacità di avere risultati in un


tempo accettabile.

• Efficienza spaziale, cioè lo spazio necessario a memorizzare i dati


deve essere fisicamente accettabile.

Si definisce per un esecutore la soluzione effettiva di un problema allorchè l’esecutore è in


grado di:

• interpretare i dati di ingresso;

• interpretare la descrizione di tale soluzione, e quindi di associare


ad essa le azioni che deve compiere per eseguirla;
• compiere tali azioni, completando l’esecuzione in un tempo finito.

L' algoritmo è il procedimento risolutivo di un problema. Esso è l’insieme di regole che, eseguite
ordinatamente, permettono di ottenere i risultati del problema a partire dai dati a
disposizione.

Algoritmo

Un algoritmo è un elenco finito di istruzioni univocamente interpretabili, ciascuna delle quali deve
essere precisamente definita e la cui esecuzione si arresta per fornire i risultati di una classe di
problemi per ogni valore dei dati di ingresso.

Un formalismo che permette di rappresentare gli algoritmi è il diagramma di flusso.

Proprietà di un algoritmo

• Non ambiguità: le istruzioni devono essere univocamente


interpretabili dall’esecutore dell’algoritmo.

• Eseguibilità: l’esecutore deve essere in grado, con le risorse a


disposizione, di eseguire ogni istruzione in un tempo finito.

• Finitezza: l’esecuzione di un algoritmo deve terminare in un


tempo finito per ogni insieme di valori in ingresso.

Il termine algoritmo...

...deriva dal matematico arabo Al-Khowarzimi del IX secolo d.c. che suggerì un metodo per
sommare 2 numeri rappresentati dal sistema numerico Hindu.
Nel medioevo con il termine algorismus si indicava il complesso di operazioni nel calcolo
numerico con numeri arabi.
Oggi con il termine algoritmo si indica la sequenza finita di passi effettuabili per risolvere una
classe di problemi in un tempo finito.

Esempi di Algoritmi

Calcolare la soluzione di ax + b = 0

1. leggere i valori di a e di b
2. calcolare -b
3. dividere -b per a e assegnare il risultato ad x
4. scrivere x
Problema del trasporto della capra, del lupo e del cavolo da una sponda ad un'altra del fiume

1. Porta la capra sull'altra sponda


2. Torna indietro
3. Porta il cavolo sull'altra sponda
4. Porta la capra indietro
5. Porta il lupo sull'altra sponda
6. Torna indietro
7. Porta la capra sull'altra sponda

Calcolare il mcm (minimo comune multiplo) di due numeri naturali diversi da zero

1. Scomporre i numeri in fattori primi


2. scegliere i fattori comuni
3. scegliere quelli con esponente più piccolo
4. moltiplicare i numeri trovati tra loro
Linguaggio naturale - Notazione lineare

Nella rappresentazione di un algoritmo si possono riconoscere tre classi di istruzioni:

• istruzioni di ingresso e uscita (dati e risultati del problema);

• istruzioni di assegnamento (P <- A con il significato assegna il nome


logico P al valore A);

• istruzioni di controllo: sono quelle istruzioni che modificano la


sequenza dell’esecuzione;

• alterazione incomdizionata;

• alterazione condizionata.

Esempio

Calcolare il prodotto di due numeri interi positivi A e B supponendo che l’esecutore conosca
solo le operazioni di somma, sottrazione e confronto fra numeri.

N.istruzione Istruzione

1. leggi A e B

2. P <- A

3. se B è uguale 1 vai all’istruzione 7

4. P <- P+ A

5. B <- B - 1

6. vai all'istruzione 3

7. scrivi P

8. fine

Tabella di traccia
L’algoritmo può essere testato attraverso l’utilizzo di una tabella di Traccia.

Vediamo qui di seguito l'esempio dell’algoritmo precedente eseguito per i valori di ingresso 7 e
4

istruzione A B P
inizio
Leggi A e B 7 4 -
P <- A - - 7
Bè=a1?
P <- P + A - - 14
B <- B - 1 - 3 -
Bè=a1?
P <- P + A - - 21
B <- B - 1 - 2 -
Bè=a1?
P <- P + A - - 28
B <- B - 1 - 1 -
Bè=a1?
Scrivi P
Algoritmi equivalenti
Due algoritmi si dicono equivalenti quando :

• hanno gli stessi dati in ingresso;

• hanno gli stessi dati in uscita;

• in corrispondenza dello stesso input producono lo stesso output.

Inoltre:

• forniscono lo stesso risultato;

• possono avere diversa efficienza;

• possono essere profondamente diversi.

Esempio di algoritmi equivalenti

Calcolo del MCD (massimo comune divisore) fra i numeri m ed n.

1. Algoritmo A:

2. calcola l'insieme M dei divisori di m;

3. calcola l'insieme N dei divisori di n;

4. calcola l'insieme K dei divisori comuni fra M ed N;

5. il risultato è il massimo di K.

6. Algoritmo B (metodo di Euclide):

finchè m è diverso da n

7. se m > n sostituisci a m il valore (m-n);

8. altrimenti sostituisci a n il valore (n-m);

9. il MCD è il risultato finale ottenuto quando m ed n diventano


uguali.
Diagramma a blocchi

Uno dei primi e più diffusi formalismi per la descrizione degli algoritmi si avvale di una
dislocazione di blocchi contenenti le istruzioni e connessi mediante frecce, ed è detto
Diagrammi a Blocchi (DaB).

Ogni blocco ha un ramo di ingresso e uno o più rami di uscita; collegando tra loro i vari
blocchi, aventi diverse forme, attraverso linee orientate si ottiene il diagramma a blocchi detto
anche diagramma di flusso.

Le forme dei blocchi sono:

• : indica una operazione, infatti in esso si scrive un'azione


Rettangolo
elementare (come ad esempio un assegnamento).

• : indica una diramazione, cioè la verifica di una condizione e


Rombo
la contrassegnazione degli archi uscenti in conformità con il
risultato della verifica.

• Parallelogramma : indica una operazione di ingresso o uscita.


• : indica l’inizio o la fine dell’algoritmo.
Ellissi

Limiti dei diagrammi a blocchi

• I DaB sono generalmente illeggibili, non si riesce a seguire


l’algoritmo soprattutto quando superano le dimensioni di un
semplice esercizio didattico. La lettura andrebbe fatta un po’
dall’alto un po’ dal basso senza un ordine preciso.

• I DaB sono facilmente esposti ad errori logici, bastano pochi


accavallamenti di cicli per perdere il filo del controllo. Ciò nasce
dalle correzioni consentite da un indisciplinato uso delle frecce che
causa un proliferare di errori logici che si accentua con la
complessità del problema e l’inesperienza del risolutore.

• Scarsa praticità dovuta alla natura grafica bidimensionale.

• Difficoltà di riconoscimento della struttura di controllo.

La soluzione a questi problemi consiste nell’imporre una disciplina di composizione che eviti
cattive strutturazioni degli algoritmi.

L’idea base è che:


un algoritmo deve essere letto dall’alto al basso secondo un ordine sequenziale di esecuzione .
Ciò non significa che non possono esistere dei cicli o dei test, ma che questi siano strutturati in
modo da poter essere considerati come un unico blocco operativo con un unico ingresso e una
sola uscita.
Si possono distringuere:

1. blocchi semplici;

2. blocchi composti;

tutti con un unico punto di ingresso e un unico punto di uscita.

Notazione lineare strutturata

La notazione lineare strutturata si basa su un insieme di schemi di composizione che sono detti
anche costrutti di controllo strutturati. Ciascuno di essi verrà descritto sintatticamente da
alcune regole che sono sostanzialmente le stesse che si ritrovano nei linguaggi di
programmazione tipo PASCAL o C.

COSTRUTTO SEQUENZA (o begin end)

Questo schema prende ordinatamente un numero arbitrario di blocchi strutturati S1, S2 ... Sn
e ne costruisce la sequenza secondo quanto espresso dal diagrammi a blocchi.
Sintassi NLS:

begin
S1;
S2;
.
.
.
Sn
end
COSTRUTTO SCELTA(o if then else)

Questo schema valuta inizialmente una condizione COND, se questa è VERA ( true ), viene
successivamente eseguito in blocco strutturato S1 altrimenti ( false )viene eseguito il blocco
strutturati S2 secondo quanto espresso dal diagrammi a blocchi.

Sintassi NLS:

if COND then S1 else S2;

COSTRUTTO CICLO (o while do)

Questo schema prevede una condizione COND, detta GUARDIA del ciclo, da valutare
inizialmente. Se questa è falsa si esce immediatamente dal costrutto, altrimenti si esegue
ciclicamente il blocco strutturato S, detto CORPO del ciclo fino a quando la condizione resterà
VERA ( true ).

Sintassi NLS:

while COND then S ;


Esempio n°1

Dati due numeri interi positivi effettuare il loro prodotto con il metodo delle addizioni
successive

Esempio n°2

Sommare i primi N interi positivi

Esempio n°3

Calcolare il quadrato di un numero N intero positivo utilizzando la somma dei primi N


numeri dispari
Linguaggio C
Elementi fondamentali del linguaggio C

Introduzione

• Progettato nel 1972 da Ritchie presso i laboratori AT & T Bell, per


poter riscrivere in un linguaggio il codice del sistema operativo
Unix.

• La definizione formale si è avuta nel 1978 da Kernigham e Ritchie.

• Nel 1983 venne nominato un comitato per creare uno standard


ANSI (American National Standard Institute), e lo standard
ANSI C fu adottato alla fine del 1989.

• Una versione estesa del C è il C++, progettata con lo scopo di


sfruttare la programmazione agli oggetti.

Il linguaggio C è considerato un linguaggio di medio livello, non perchè sia meno potente di
linguaggi ad alto livello come il Basic o il Pascal, ma perchè esso riunisce i migliori elementi di
un linguaggio ad alto livello con possibilità di controllo e flessibilità di un linguaggio a basso
livello come l'Assembler.

Il linguaggio C è caratterizzato dai seguenti punti:

• Elevato potere espressivo:

• tipi di dato primitivi e tipi di dato definibili dall’utente;

• strutture di controllo (programmazione strutturata, funzioni e


procedure).

• Caratteristiche di basso livello (gestione delle memoria, accesso


alla rappresentazione).

• Stile di programmazione che incoraggia lo sviluppo di programmi


per passi di raffinamento successivi (sviluppo top-down).

• Sintassi definita formalmente.

A tal punto possiamo affermare che le sue caratteristiche principali sono:

1. portabilità;

2. non e' un linguaggio fortemente tipizzato;

3. consente la gestione di bit, byte e indirizzi;


4. utilizza solo 32 parole riservate (27 K&R, 5 Ansi);

5. linguaggio strutturato;

6. linguaggio per programmatori.

Parole chiave dell'ANSI C

Le parole chiave sono parole riservate, che quindi non possono essere usate come identificatori
(ovvero le variabili, le funzioni e gli oggetti definiti dall'utente).
Esse sono solo 32, 27 derivanti dallo standard Kernigham & Ritchie e 5 aggiunte dalla
standardizzazione ANSI.
Tali comandi sono i seguenti:

auto double int struct


break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while
Elementi fondamentali del linguaggio C
Struttura di un programma C

Nel linguaggio C il principale componente strutturale è la funzione. Attraverso questa si


creano programmi modulari.
E' possibile riutilizzare le funzioni in più situazioni all'interno di un programma.

E' possibile creare un programma C costituito solamente da istruzioni create dal


programmatore.
Ma ogni compilatore C comprende una libreria di funzioni standard che servono per eseguire
le operazioni più comuni di Input/Output (oltre varie routine).
Per potere fare uso di funzioni standard all'interno di un programma, la funzione richiesta
viene richiamata e al codice del programma viene aggiunto il codice oggetto della funzione che
si trova nella libreria standard attraverso il linker.
Il linker è quel programma che esegue il collegamento (linking) di diverse funzioni compilate
in un unico programma. L'output del linker è il programma eseguibile.

Struttura generale di un programma

<programma> ::= < dichiarazioni globali >


<funzione> {<funzione>}
<funzione> ::= <tipo_ritorno> <nome_funz>
(<elenco_argomenti>)
{ <sequenza_istruzioni>
}
Nel testo di un programma C possono esserci:

• parole chiave;

• commenti, ovvero sequenze di caratteri usati per introdurre note


esplicative al codice del programma e ignorate dal compilatore;

• caratteri e stringhe. Queste ultime sono rappresentate da una


sequenza di caratteri racchiusi tra i doppi apici ( ad es. "ciao" );

• numeri interi reali, valori costanti;

• identificatori, cioè variabili, costanti, funzioni, etichette.

I commenti sono sequenze di caratteri ignorate dal compilatore.


Esse devono essere racchiuse tra uno / seguito dall'asterisco * ( per cominciare ) e asterisco * e
/ ( per terminare ), in tal modo:

/* ......... */

Ad esempio:
/* questo e‘ un commento dell’autore */

L'insieme dei caratteri disponibili e‘ dipendente dall' implementazione. In genere si utilizza il


codice ASCII esteso a 256 caratteri.

Alcuni tra i caratteri speciali sono:

newline \n
tab \t
backspace \b
form feed \f
carriage return \r
codifica ottale \OOO (con O cifra ottale 0-7)

I numeri interi rappresentano numeri relativi (quindi con segno):

da 2 byte a 4 byte

base decimale 12 70000, 12L


base ottale 014 0210560
base esadecimale 0xFF 0x11170

Per i numeri reali esistono varie notazioni:

da 24.0 2.4E1 a 240.0E-1

• Suffissi: l, L, u, U ( interi-long, unsigned); f, F (reali - floating)

• Prefissi: 0 ( ottale ); 0x, 0X( esadecimale )

Nel C, inoltre, le lettere maiuscole e minuscole sono considerate diverse (case sensitive).

Il testo del programma scritto dal programmatore è il codice sorgente (e rappresenta l'input
del compilatore C).

La traduzione di tale codice sorgente in codice macchina viene detto invece codice oggetto.

Compilazione di un programma C

La creazione e lo sviluppo di un programma C si compone dei seguenti passi:

1. creazione del programma;

2. compilazione del programma;

3. collegamento (linker) del programma con le funzioni di libreria


richieste.

Quando si scrive un programma in C è possibile suddividere il codice sorgente in più file che
possono così essere compilati separatamente .
Dopo aver compilato viene eseguito il linking, includendo le routine di libreria, per formare un
unico file di codice oggetto completo.

Suddivisione concettuale della memoria per un programma C

Un programma C compilato utilizza quattro regioni di memoria distinte:

Stack
Heap
Variabili globali
Codice del programma

La prima regione contiene il codice del programma, la seconda contiene le variabili globali.
Le successive sono:

• lo stack che viene utilizzato per le operazioni eseguite dal


programma. Esso contiene gli indirizzi di ritorno delle chiamate
alle funzioni, gli argomenti delle funzioni e le variabili locali;

• l'heap che è quella regione di memoria libera che il programma


può utilizzare per oggetti come le liste e gli alberi, attraverso le
funzioni di allocazione dinamica.
Tipi di dato

In C i tipi di dati principali sono 5, e precisamente:

• char ( caratteri )

• int ( numeri interi )

• float ( numeri in virgola mobile )

• double ( numeri in virgola mobile doppi )

• void ( non valori )

Tutti gli altri tipi di dati usati in C si basano su questi 5 tipi.

Escludendo il tipo void, i tipi di dati sono dotati di modificatori ( o qualificatori ) di tipo, che
alterano il significato di tipo base per adattarlo alle varie richieste. Essi sono:

1. signed

2. unsigned

3. long

4. short

Il modificatore long può anche essere applicato a double.

Nella seguente tabella sono elencate tutte le combinazioni di tipi di dati seguite dall'ANSI C,
con le rispettive dimensioni approsimative in bit, e l'intervallo minimo richiesto.
TIPO BIT INTERVALLO MINIMO
char 8 da -127 a 127
unsigned char 8 da 0 a 255
signed char 8 da -127 a 127
int 16 da -32767 a 32767
unsigned int 16 da 0 a 65535
signed int 16 da -32767 a 32767
short int 16 da -32767 a 32767
unsigned short int 16 da 0 a 65535
signed short int 8 da -32767 a 32767
long int 32 da -2147483674 a 2147483674
signed long int 32 da -2147483674 a 2147483674
unsigned long int 32 da 0 a 4294967295
float 32 precisione di 6 cifre (37)
double 64 precisione di 10 cifre (308)
long double 128 precisione di 10 cifre (4932)

Identificatori e variabili

Lo standard ANSI C definisce identificatori quei nomi che "identificano", ovvero attraverso
i quali si fa riferimento a:

• variabili;

• funzioni;

• etichette;

• e altri oggetti definiti dall'utente.

Un identificatore è costituito da uno o piu' caratteri, e deve seguire le seguenti regole:

• il primo carattere deve essere una lettera o un underscore ( _ );

• i caratteri successivi possono essere lettere, numeri o underscore.

Gli identificatori possono avere qualsiasi lunghezza ma sono significativi:

• per nomi esterni: 6 caratteri

• per nomi interni: 31 caratteri

Le variabili

Una variabile è un contenitore, una cella di memoria identificata con un nome simbolico. Essa
rappresenta un dato che può cambiare il proprio contenuto durante l'esecuzione di un
programma.

Dichiarazione delle variabili


<dichiarazione_variabile> ::=
<tipo > <elenco_di_variabili>
<elenco_di_variabili> ::=
<ident> {,<ident>}
Esempio
int P,k,l;
long int var;

Le variabili possono essere dichiarate:

• all'interno delle funzioni ( variabili locali )

• nella definzione degli argomenti delle funzioni ( parametri formali


)

• all'esterno di tutte le funzioni ( variabili globali )

Le variabili locali possono essere utilizzate solo dalle istruzioni che si trovano all'interno del
blocco in cui sono dichiarate, e quindi non sono accessibili all'esterno di tale blocco.
Per blocco si intende quella parte di codice tra una parentesi graffa aperta e la corrispondente
parentesi graffa chiusa.
La vita di queste variabili è legata all'esecuzione del blocco di codice in cui sono dichiarate.
Infatti la variabile locale viene creata nel momento in cui si entra nel blocco e viene rilasciata
all'uscita, quindi il suo contenuto viene perso all'uscita dal blocco.
E' possibile inizializzare una variabile locale ad un determinato valore; tale valore verrà
assegnato alla variabile ogni volta che si accede al blocco. solo passaggio per valore passaggio
per variabile: passare per valore l'indirizzo

I parametri formali vengono usati quando una funzione deve utilizzare gli argomenti. In
questo caso la funzione deve predisporre un insieme di variabili che hanno lo scopo di ricevere
i valori degli argomenti.
I parametri formali di una funzione si comportano come una qualunque altra variabile locale
all'interno della funzione.
Bisogna assicurarsi che i parametri formali siano dello stesso tipo dei parametri attuali, il C
non effettua nessun controllo in tal senso.
L'ANSI C fornisce il meccanismo dei prototipi di funzione che possono essere utilizzati per
verificare che gli argomenti utilizzati per chiamare a funzione siano compatibili con quelli
definiti nella funzione stessa.

Le variabili globali a differenza di quelle locali sono note all'intero programma e possono
essere usate in qualsiasi punto del codice.
Esse conservano il proprio valore durante l'intera esecuzione del programma.
E' possibile dichiarare le variabili globali in qualsiasi punto prima del suo uso, ma
normalmente vengono dichiarate all'inizio del programma.
Se una variabile globale e una locale hanno lo stesso nome, ogni riferimento della variabile
all'interno del del blocco di codice in cui è dichiarata la variabile locale farà riferimento a
quest'ultima.
Il C riserva un area di memoria per le variabili globali.

Vantaggi:
• sono utili quando piu' funzioni utilizzano gli stessi dati.

Svantaggi:

• impiegano la memoria per tutta la durata del programma e non


solo quando e' necessaria;

• l'uso di una variabile globale dove potrebbe essere usata una


locale rende le funzioni meno generali perche il loro
funzionamento implica la conoscenza di un elemento esterno alla
funzione;

• usando molte variabili globali si rischia di perdere il controllo e


cio' puo' portare ad errori dovuti ad effetti collaterali indesiderati
ed imprevisti.
Modificatori del tipo di accesso

Nello standard ANSI sono stati introdotti due modificatori ( o qualificatori ) che servono a
controllare l'accesso e la modifica delle variabili, quindi come possono essere lette e
modificate.
Tali modificatori di accesso sono:

• const

• volatile

• Quando una variabile è di tipo const, essa non può essere


modificata dal programma, ma gli si può assegnare un valore
iniziale che sarà mantenuto per tutta la durata dell'esecuzione del
programma.

Esempio

const int x = 5;

Il modificatore const è utile per evitare che una funzione modifichi


gli oggetti puntati dagli argomenti della funzione stessa.
Infatti quando a una funzione viene passato un puntatore e nella
dichiarazione degli argomenti tale puntatore era specificato di tipo
const allora la funzione non può modificare la variabile a cui
questo punta.

• Il modificatore volatile viene utilizzato per indicare al compilatore


che il valore di una variabile può essere modificato in modo non
esplicitamente specificato dal programma.
Ad esempio l'indirizzo di una variabile globale può essere passata
alla routine dell'orologio di sistema, per usare tale variabile per
conservare l'ora. In tale situazione il contenuto della variabile
verrà modificato senza che il programma faccia un assegnamento.

E' possibile usare insieme i due modificatori.


Specificatori di classe di memorizzazione

In C esistono 4 specificatori di classe di memorizzazione, e sono:

• extern

• static

• register

• auto

Essi richiedono al compilatore di memorizzare in un determinato modo le variabili precedute


nella dichiarazione da uno degli specificatori.

• Extern
Con il linguaggio C viene fatta una compilazione separata dei vari
moduli di un programma, che vengono uniti attraverso il linker.
Vi deve essere per cui un modo per comunicare a tutti i file del
programma le informazioni sulle variabili globali.
E' necessario che tali variabili non siano dichiarate in ogni file di
un programma (formato da più file), ma in un unico file e in tutti
gli altri tali variabili saranno precedute nella dichiarazione da
extern.

• Static
Le variabili di tipo static sono permenenti all'interno della
funzione o del file in cui sono dichiarate.
Vediamo adesso le variabili static di tipo locale e globale.

Variabili locali static


Quando a una variabile locale si applica lo specificatore static, dal
compilatore viene creata una cella di memoria permanente, il cui
contenuto è noto solo all'interno del blocco in cui è dichiarata.
Una variabile locale static conserva il proprio valore tra due
chiamate di funzione.

Variabili globali static


Quando si applica lo specificatore static a una variabile globale il
compilatore crea una variabile globale nota solo all'interno del file
in cui è stata dichiarata.
Quindi altri file non possono modificare il suo contenuto.
• Register
Originariamente questo specificatore serviva per conservare il
valore di una variabile in un registro della CPU, in modo da
potervi accedervi direttamente senza richiedere accessi in
memoria.
Oggi con lo standard ANSI, register serve, in generale, per
l'accesso all'oggetto più rapido possibile.
Inoltre lo standard ANSI permette di utilizzare lo specificatore
register con ogni tipo di variabile ( non solo al tipo char e int ).

Le costanti

Una costante rappresenta un valore fisso che non può essere alterato durante l'esecuzione del
programma.
Nel linguaggio C le costanti possono essere di qualsiasi dei tipi principali.

La dichiarazione di una costante associa al nome della costante, l'identificatore, il valore ( che
può essere un numero, un carattere o una stringa di caratteri ).
Prima di utilizzare un identificatore, nella dichiarazione di una costante, deve essere definito.

Esempi:

const float pigreco = 3.1415926;

const int primo = 0;

Invece le costanti di tipo carattere devono essere racchiuse tra apici.


Esempi:

printf ( 'ciao' );

char carattere = 'a';

Nota:
Una direttiva del precompilatore C permette di ottenere lo stesso risultato di const, essa non
alloca spazio in memoria ed è #define.

Esempio:

# define pigreco 3.1415926


Gli operatori

Nel linguaggio C sono definite 4 classi di operatori:

• aritmetici

• relazionali

• logici

• bit-a-bit

Inoltre vi sono altri operatori dedicati a compiti specifici, come ad esempio l' operatore di
assegnamento, che è necessario introdurre per primo.

Operatore di assegnamento
La forma generale in cui si usa questo operatore è:

nome_variabile = espressione

Nel lato sinistro di questo assegnamento deve trovarsi una variabile o un puntatore ( non
possono esserci funzioni o costanti ).
Mentre nel lato destro l'espressione può essere una semplice costante o complessa quanto si
voglia.

In C è possibile specificare più vriabili con un unico assegnamento.


In tal caso tutte le variabili assumono lo stesso valore.

Esempio:

a = b = c = 7;

Operatori aritmetici
Nella seguente tabella sono riportati gli operatori aritmetici del C
OPERATORE ARITMETICO AZIONE
- sottrazione e meno unario
+ addizione
* moltiplicazione
/ divisione intera
% modulo, resto della divisione intera
-- decremento
++ incremento
• L'operatore modulo %, in C così come in altri linguaggi, fornisce il
resto della divisione intera. Tale operatore non puo' essere usato
sui tipi in virgola mobile, ovvero float e double.

• L'incremento ++ e il decremento - - aggiungono e sottragono,


rispettivamente, una unità al proprio operando.
Esempi:

x = x + 1;
equivale a ++x;

x = x - 1;
equivale a x - -;

Gli operatori di incremento e decremento possono precedere ( ++


x o --x) o seguire l'operando ( x++ o x - - ).
Tuttavia queste due forme hanno un significato diverso, infatti
quando l'operatore precede l'operando, il C esegue l'incremento
( o decremento ) prima di fornire il valore dell'operando
all'espressione. Se invece l'operatore segue l'operando, il C
fornisce il valore dell'operando e poi lo incrementa ( o
decrementa).

• La priorita' degli operatori aritmetici è la seguente:


massima ++ --
- (unario)
* / %
minima + -

Operatori relazionali e logici


Gli operatori relazionali mettono in relazione un valore rispetto un altro, mentre gli operatori
logici indicano il modo in cui le relazioni possono essere connesse.
L'idea di base di questi operatori si basa sul concetto di: VERO ( 1 ) e FALSO ( 0 ).
Vediamo tali tipi di operatori nella seguente tabella
OPERATORE RELAZIONALE AZIONE
> maggiore
>= maggiore uguale
< minore
<= minore uguale
== uguale
!= diverso
OPERATORE LOGICO AZIONE
&& AND
|| OR
! NOT

• Gli operatori logici e relazionali hanno una priorità inferiore


rispetto gli operatori aritmetici.

• L'operatore logico OR esclusivo ( XOR ) in C non è definito ma


può essere creato utilizzando gli operatori logici.

• La priorita' degli operatori logici e relazionali è la seguente:


massima !
> >= <= <
== !=
&&
minima ||

Operatori bit-a-bit
Gli operatori bit-a-bit servono per spostare o controllare i bit delle word o dei byte
corrispondenti ai tipi char e int.
OPERATORE BIT-A-BIT AZIONE
& AND
| OR
^ XOR
~ Complemento a uno
>> Scorrimento a destra
<< Scorrimento a sinitra

• Gli operatori bit-a-bit possono servire per realizzare i driver dei


dispositivi, in quanto permettono di mascherare determinati bit.

• Questi operatori non possono essere applicati ai tipi float, double,


long double e void.

• Gli operatori di scorrimento di bit spostano tutti i bit di una


variabile verso destra ( >> ) o verso sinistra ( << ).
Con tale operazione si perde un bit a un'estremintà e all'altra
estremità si pone un nuovo bit posto a 0.
Gli operatori

Vediamo adesso altri tipi di operatori definiti dal linguaggio C

• operatore ?

• operatori per puntatori & e *

• operatore sizeof

• operatore virgola

• operatori punto ( . ) e freccia ( -> )

Operatore ?
Attraverso questo operatore ternario ? e’ possibile sostituire le istruzioni del tipo if then else.
La forma generale di tale operatore
Espr1 ? Espr2 : Espr3 ;
Se Espr1 e’ vera viene valutata Espr2 e l’intera espressione è uguale al valore di Espr2.
Se Espr1 e’ falsa viene valutata Espr3 e l’intera espressione è uguale al valore di Espr3.

Esempio

x = 100
y = x > 0 ? 20 : 30

In questo esempio, poichè x risulta > 0, alla variabile y viene assegnato il valore 20.

Operatori per puntatori * e &


E' necessario anzitutto dire che un puntatore è una variabile che contiene un indirizzo di
memoria di un'altra variabile. Quindi una variabile puntatore è una variabile che serve a
contenere un puntatore a un determinato oggetto.
L'operatore unario & restituisce l’indirizzo di memoria dell’operando cui e’ applicato.

Esempio

c = &prova;

alla variabile c viene assegnato l'indirizzo in cui si trova prova.

L'operatore * e’ il complemento di &, esso e’ un operatore unario che restituisce il valore della
variabile che si trova nell’indirizzo indicato nella variabile che lo segue.
Le variabili che contengono puntatori devono essere dichiarate come tali nel seguente modo:

<tipo base> *<identificatore>;


Esempio

char *r;
int *t;

In tal modo sono state dichiarate: r come puntatore a un tipo carattere e t come puntatore a
un tipo intero.

Vediamo adesso un programma che utilizza entrambi gli operatori per poter inserire il valore
56 nella variabile i, che verrà visualizzata.

void main (void)


{
int i,j;
int *pint;
j = 56;
pint = &j;
i = *pint;
printf("%d",i);
}

Operatore sizeof
L'operatore unario sizeof ritorna la dimensione in byte del tipo racchiuso tra parentesi, nel
caso in cui viene applicato a una variabile non è necessaio utilizzare le parentesi tonde.
La forma generale è:

sizeof(<identificatore_tipo>);

Esempio

int a;
printf ("%d",sizeof a);
printf ("%f",sizeof(float));

Operatore virgola

L’operatore virgola consente di unire due espressioni, di cui quella a sinistra viene considerata
del tipo void, per cui il suo valore viene ignorato.
La valutazione dell’espressione a destra del segno di uguale costituisce il valore dell’intera
espressione.

Esempio

x = (y=5, y+2)
in tal modo viene assegnato a y il valore 5 e ad x il valore 7.

Operatori punto ( . ) e unione ( -> )


Questi puntatori permettono di accedere ai singoli elementi delle strutture e unioni.
L’operatore punto ( . ) viene usato per accedere alla variabile struttura o unione in modo
diretto.
L’operatore freccia ( -> ) viene usato per accedere alla variabile struttura o unione tramite
puntatore.

Le espressioni

Un’espressione e‘ una regola che serve per il calcolo di un valore.


Essa è una combinazione di operandi tramite degli operatori.
Ad esempio:
3 * (17 - 193) % 19
(3 < 7) && (4 <= 5) or (X > 10)
Quando nell'espressione compaiono delle variabili a ciascuna di queste deve essere gia` stato
attribuito un valore: l’espressione utilizza il valore corrente della variabile.

Il linguaggio C è basato sulle espressioni che possono essere costituite da dati ( variabili o
costanti ) e da operatori.
Quando un'espressione è formata da dati di tipo diverso è necessario convertire tutto nello
stesso tipo.
Ad esempio i char e short int vengono convertiti in int.

Conversioni cast

E’ possibile forzare una espressione a essere di un tipo specificato usando il costrutto di cast
che si usa nella forma:

(<tipo>) espressione

Esempio

int x;
(float) x/2;

Con la conversione cast (float) viene considerata anche la parte frazionale della divisione.

Notazione abbreviata delle espressioni:


<variabile> <operatore> = <espressione>
e’ equivalente <variabile> = <variabile> <operatore> <espressione>

Valutazione delle espressioni

Nelle espressioni, gli operatori sono valutati secondo una precedenza prestabilita (altrimenti
parentesi tonde), e seguendo opportune regole di associatività .

La precedenza degli operatori è stabilita dalla sintassi delle espressioni.


a + b * c equivale a a + (b * c)

L'associatività è ancora stabilita dalla sintassi delle espressioni.


a + b + c equivale a (a + b) + c
Strutture di controllo

Vengono presentate adesso le istruzioni di controllo che possono costituire un programma:

• selezione

• if

• if-else-if

• ?

• switch

• iterazione

• for

• while

• do-while

• salto

• return

• goto

• break

• exit

• continue

• etichetta
• goto

• case

• default

• espressione

• blocco

Le istruzioni etichetta vengono trattate insieme ad altre istruzioni ( case e default insieme allo
switch, e goto con l'istruzione di salto ).
Le istruzioni di blocco sono gruppi di istruzioni connesse fra loro. Ogni blocco si apre e si
chiude con le parentesi graffe ( { blocco } ).
Istruzioni di selezione

Vediamo adesso in dettaglio tutte le istruzioni di selezione ( le istruzioni if e switch vengono


dette anche istruzioni condizionali ):

• Istruzione if

if ( condizione ) istruzione
else istruzione

Queste regole servono per utilizzare if-else . L' istruzione nell' if


può essere singola o esserci un intero blocco, oppure potrebbe
essere un' istruzione vuota .
L' else è opzionale, quindi potrebbe anche non essere presente.

Vediamo come funziona:

• se la condizione è vera o diversa da 0 viene eseguito ciò che c'è


scritto nell' if;

• altrimenti, se è presente, viene eseguita l' <istruzione > dell' else.

Esempio

#include "stdio.h"
#include "stdlib.h"
void main(void)
{

int magico,tentativo;
magico = rand( ); /* genera un numero casuale */
printf("Indovina il numero");
scanf("%d", &tentativo);
if (tentativo == magico) printf(" ESATTO");
else printf("ERRORE");

• Istruzioni if annidati
Gli if annidati possono trovarsi all'interno di altri if o else.
Lo standard ANSI specifica che al massimo ci siano 15 livelli di
annidamento.
Vediamo adesso come è possibile applicare l'annidamento:

if (i)
{
if (j) istruzione1;
if (k) istruzione2;
else istruzione3;

}
else istruzione4;

• Istruzione if else if
In tale costrutto sono presenti varie condizioni che vengono
valutate dall'alto verso il basso.
La forma generale è la seguente:

if (condizione) istruzione;
else if (condizione) istruzione;
else if (condizione) istruzione
.
.
else istruzione /* else associato al primo if */

• Istruzione switch
L'istruzione di selezione switch è a più opzioni, ovvero confronta il
valore della variabile con un elenco di costanti specificate
dall'istruzione case.
Quando viene trovata una corrispondenza tra la variabile e la
costante del case vengono eseguite le istruzioni di quel case, fino
ad arrivare al break, che fa uscire dallo switch.
L'istruzione break indica un salto, e oltre che nello switch si usa
anche nelle istruzioni di iterazione.
L'istruzione default, che è opzionale, viene eseguita solo nel caso in
cui non viene verificata nessuna corrispondenza. Quando non si
trova nessuna corrispondenza e non c'è il default non viene
eseguita nessuna istruzione.

L'istruzione switch si presenta nel seguente modo:

switch (variabile)
{

case costante1: sequenza di istruzioni break;


case costante2: sequenza di istruzioni break;
.
.
case costanten: sequenza di istruzioni break;
default sequenza di istruzioni

Le costanti dei case devono essere sempre diverse.


Inoltre è possibile far uso anche di switch annidati.
Istruzioni di iterazione

• Istruzione for
La forma generale di tale istruzione è:

for ( inizilizzazione; condizione; incremento)

• L'inizializzazione e' una espressione, nella sua forma piu' semplice


e' una assegnazione e serve per impostare la variabile di controllo
del ciclo.

• La condizione e' una espressione relazionale usata per valutare la


variabile o le variaibili di controllo del loop.
L'esecuzione del ciclo prosegue fino a quando questa condizione e'
vera, quando diviene falsa l'esecuzione prosegue con l'istruzione
successiva al for.

• L'incremento e' una espressione che definisce il modo in cui la


variabile di controllo cambia il suo valore ad ogni ripetizione del
loop.

Esempio

#include "stdio.h"
void main(void)
{

int x;
for(x=1; x<=100; x++) printf("%d",x);

Il test della condizione viene eseguto al momento del'entrata nel


loop, prima delle istruzioni.

Vediamo adesso 3 esempi che impiegano in maniera diversa il ciclo


for.

Esempi

#include "stdio.h"
void main(void)
{

int x;
for(x=100; x>=0; x--) printf("%d",x);

}
In questo caso la variabile è inizializzata a 100 e ad ogni passo
viene decrementata fino a quando si arriva a 0.

#include "stdio.h"
void main(void)
{

int x;
for(x=1; x<=100; x+=5) printf("%d",x);

In questo esempio si ha un incremento di x di 5 unità ad ogni ciclo,


e fino a quando la condizione risulta essere vera verrà visualizzato
il valore della variabile x.

#include "stdio.h"
void main(void)
{

int x;
for(x=1, y=1; x+y<=100; x++, y+=6)
printf("%d",x+y);

Come si vede dall'esempio è anche possibile utilizzare nel ciclo 2 o


più variabili attraverso l'uso dell'operatore virgola
nell'inizializzazione.
Poichè nessuna delle tre espressioni dell'istruzione for è
indispensabile è possibile creare un loop infinito in tal modo:

for (;;) printf("questo ciclo non finisce mai");

• Istruzione while
Così come il for, anche questa è un'istruzione di ciclo, che
controlla la condizione all'inizio del ciclo. La sua forma generale
è:

while (condizione) istruzione

L'istruzione può essere singola, vuota o ci può essere un blocco di


istruzioni.
Fino a quando la condizione rimane vera ( o diversa da 0 ) il ciclo
viene ripetuto.

Invece il costrutto do-while verifica la condizione alla fine del


ciclo.
La forma usata in C è la seguente:

do
{istruzione
}while (condizione)

Esempio
void menu(void)/* la scelta in un menu */
{
char car;
printf("1 per prima scelta");
printf("2 per seconda scenta");
printf("3 per terza scelta");
do
{
printf("inserisci la tua scelta");
car = getchar( ) /* viene letta e assegnata a car la
scelta fatta da tastiera*/
switch(car)
case "1": prima( ); break;
case "2": seconda( ); break;
case "3": terza( ); break;
}
while(car !='1' && car !='2' && car !='3')
}
Istruzioni di salto

• Istruzione return
Questa istruzione permette di uscire da una funzione:

return espressione

Essa consente il salto nel punto in cui e' stata chiamata la


funzione. Inoltre l'espressione è opzionale, nel caso in cui c'è
esprime il valore restituito dalla funzione.

• Istruzione goto
Tale istruzione non viene molto usata in quanto rende poco
leggibili i programmi.
Con l'istruzione goto deve essere usata un'etichetta, ovvero un
identificatore seguito dai due punti, che deve trovarsi nella stessa
funzione del goto.
La forma generale è la seguente:

goto etichetta;
...
etichetta:

• Istruzione break
L'istruzione break si può usare in 2 casi:

• per la terminazione di un loop saltando il test condizionale;

• per concludere un case in un costrutto switch.

Quando all'interno di un ciclo compare tale istruzione, il ciclo


termina e il programma riparte dall'istruzione successiva al ciclo.

• Istruzione exit
Per la terminazione dell'intero programma e possibile utilizzare
exit.
Quando durante l'esecuzione di un programma si incontra tale
istruzione si esce subito dal programma e il comando torna al
sistema operativo.

• Istruzione continue
Con questo comando invece di ottenere la terminazione di un
ciclo, provocando l'esecuzione successiva iterazione del ciclo.
Array, stringhe e puntatori
Array

Un array è un insieme ordinato di elementi dello stesso tipo; ogni elemento è individuabile
attraverso un indice che dà la posizione rispetto il primo elemento.
L'elemento i-esimo di un array V contenente m elementi è indicato con V[i].
Gli array sono memorizzati in memoria in locazioni contigue.

Array ad una dimensione


La dichiarazione di un array monodimensionale, che non è altro che un elenco di
informazioni dello stesso tipo, è la seguente:

<def_variabile_array> ::=
<tipo_base> <identificatore_var>[<dimensione>]
<dimensione> ::= <intero>

In C gli array vanno dichiarati utilizzando un valore intero a partire dall'indice 0.


Ad esempio:

int vettore[10]

e' un array di 11 elementi interi da vettore[0], ..., vettore[10].

Nota:
In C non viene fatta nessuna verifica sul superamento dei limiti di un array, per cui per
evitare di scrivere oltre il limite deve prestare attenzione chi scrive il codice.

ESEMPIO
Mostriamo un programma che permette di fare il prodotto scalare tra uno scalare e un vettore.

/*prodotto scalare*/
#include <stdio.h>
void main ( )
{
int scalare, i, a;
float vettore[8];/*questo array ha lunghezza 8*/

printf("inserisci lo scalare da moltiplicare\n");


scanf("%d",&scalare);

printf("inserisci il vettore\n");
for(i=0; i<= 4; i++)
scanf("%d",&a); vettore[i]=a;
for(i=0; i<= 4; i++)
vettore[i]= vettore[i] * scalare;

/*stampa del vettore*/


for(i=0; i<= 4; i++)
printf("%f ",vettore[i]);
printf("\n");
}

Array come argomento di funzione


E' possibile passare come argomento di una funzione un puntatore ad un array, e non un
intero array, specificandone il nome senza usare alcun indice.
Ad esempio:
void main ( )
{
int vect[5];
funzione (vect);/* a funzione viene passato l'indirizzo del vettore*/
....
}

Quando il parametro formale di una funzione è un array, può essere dichiarato come:

• puntatore ( ad es. void funzione (int *vect) );

• array dimensionato ( ad es. void funzione (int vect[4]) );

• array non dimensionato ( ad es. void funzione (int vect[ ]) ).

Con tutte e tre le dichiarazioni il compilatore si prepara a ricevere un puntatore ad un intero.


Stringhe

In C le stringhe, ovvero le sequenze ordinate di caratteri, sono rappresentate attraverso


array monodimensionali di caratteri, dove la fine della stringa è segnalata dal carattere
nullo ('\0').

I valori di tipo stringa vengono racchiusi tra doppi apici (come per i parametri della funzione
printf), e possono essere utilizzati per inizializzare variabili di tipo 'stringa'.
Ad esempio, una variabile dichiarata da
int char stringa[5] = "cane";
corrisponde alla dichiarazione char stringa[5]; seguita dalle assegnazioni
int stringa[0] = 'c';
int stringa[1] = 'a';
int stringa[2] = 'n';
int stringa[3] = 'e';
int stringa[4] = '\0';

In C esistono diverse funzioni per poter manipolare le stringhe, alcune di queste sono:
NOME FUNZIONE
strcpy (a1, a2) Copia a1 in a2
strcat (a1, a2) Concatena a2 alla fine di a1
strlen (a1) Ritorna la lunghezza di a1
strchr (a1, ch) Restituisce un puntatore alla prima occorrenza di ch in a1

Per poter usare queste funzioni deve essere incluso il file d'intestazione standard string.h.
Array multidimensionali

Con il linguaggio C è possibile utilizzare array multidimensionali; la forma più semplice è


quella a due dimensioni, in cui gli elementi dell'array possono essere individuati attaverso due
indici.

Array bidimensionali
La dichiarazione di un array bidimensionale, è uguale a quella dell'array
monodimensionale, con la differenza che in questo caso si usano 2 indici.
Infatti questo tipo di array è memorizzato in una matrice con il numero di righe individuate
dal primo indice e il numero di colonne dal secondo.

Ad esempio:

int matrice[2] [3]

e' un array di 6 elementi interi, dove matrice[0][0] contiene il valore 16, matrice[0][1] contiene
il valore 30, ecc.

0 1 2
0 16 30 23
1 35 41 19

ESEMPIO
Mostriamo un programma che permette di fare la somma di 2 array bidimensionali.

/*somma di array bidimensionali*/


#include <stdio.h>
void main ( )
{
int a[3][3], b[3][3], risultato[3][3], i, j, s;

printf("inserisci la prima matrice\n");


for(i=0; i<= 2; i++)
for(j=0; j<= 2; j++)
{
scanf("%d",&s);
a[i][j] = s;
};

printf("inserisci la seconda matrice\n");


for(i=0; i<= 2; i++)
for(j=0; j<= 2; j++)
{
scanf("%d",&s);
b[i][j] = s;
};
for(i=0; i<= 2; i++)
for(j=0; j<= 2; j++)
risultato[i][j] = a[i][j] + b[i][j];
/*stampa il risultato*/
for(i=0; i<= 2; i++)
{
for(j=0; j<= 2; j++)
printf("%d ",risultato[i][j]);
printf("\n");
}
}

Array multidimensionale
Gli array a più di due dimensioni vengono definiti array multidimensionali; la forma
generica della dichiarazione di questo tipo di array è la seguente:
<def_variabile_array> ::=
<tipo_base> <identificatore_var>[<dim1>][<dim2>]...[<dimN>]

Anche in questo caso le dimensioni devono essere degli interi.


Comunque a causa della gran quantità di memoria necessaria gli array di tre o più dimensioni
non vengono usati molto spesso.

Variabili puntatori

Gli array e i puntatori (vedi operatori per puntatori) sono oggetti legati fra loro.
Quando si incontra il nome di un array senza indice, esso rappresenta un puntatore al primo
elemento dell'array.

La dichirazione di una variabile puntatore è la seguente:


<tipo_base> *<identificatore_var_puntatore>
Il tipo base del puntatore serve per definire il tipo delle variabili a cui punterà il puntatore.

Come già visto gli operatori dei puntatori sono due:

• & serve per restituire l'indirizzo di memoria dell'operando a cui è


applicato.
Ad esempio:
pluto=&pippo;
assegna alla variabile pluto l'indirizzo di memoria della variabile
pippo.

• * serve per restituire il contenuto dell'indirizzo di memoria della


variabile che lo segue.
Ad esempio:
b=*pluto;
inserisce il valore di pluto in b.

Un utilizzo dei puntatori può essere il seguente:

float *x; /* variabile di tipo puntatore a float */


float y; /* variabile di tipo float */
...
x=&y; /* la variabile x contiene il puntatore di y */
*x=4.5; /* la variabile y contiene il valore 4.5 */

Le espressioni contenenti puntatori seguono in generale tutte le regole delle altre espressioni.
Puntatori ad array

Come già detto gli array e i puntatori sono strettamente legati fra loro.
Per accedere ad un elemento di un array oltre ad utilizzare la solita notazione attreverso
l'indicizzazione dell'array ( usando le parentesi quadre [..] ), è anche possibile usare
l'aritmetica dei puntatori. Quest'ultimo può risultare un metodo più veloce rispetto il primo.

Vediamo un esempio, considerando una porzione di codice:

char stringa[25];
char *punt;
....
punt=stringa /*con questa assegnazione punt conterrà l'indirizzo
del primo elemento dell'array stringa*/
stringa[15] /*per accedere al 16° elemento dell'array con
l'indicizzazione*/
* (punt+15) /*per accedere al 16° elemento dell'array con
l'aritmetica
dei puntatori*/

Array di puntatori
I puntatori possono anche essere disposti in un array, così come gli altri tipi di dato.
La dichiarazione di un array di puntatori a int può essere la seguente:

int *pippo[6];

Per sapere il contenuto del penultimo elemento dell'array si può scrivere:

*pippo[5]

Per assegnare invece l'indirizzo di una variabile x al secondo elemento dell'array di puntatori
si può scrivere:

pippo[1]=&x;

Puntatori a funzioni
Anche l'indirizzo di una funzione può essere assegnato a un puntatore.
L'indirizzo fisico in memoria di una funzione è il punto di accesso alla funzione stessa, per cui
è possibile richiamare la funzione attraverso il puntatore.
Funzioni
Astrazioni funzionali: le funzioni

In C si distinguono due tipi di istruzioni: semplici e strutturate, che a loro volta possono essere
predefinite o definite dall'utente.
Le astrazioni funzionali sono delle istruzioni semplici definite dall'utente .
Sia le procedure che le funzioni sono astrazioni funzionali.

La funzione è una sottoparte di programma, composta da una dichiarazione , costituita da


un nome e dai parametri formali,e da un corpo.
Ad esempio definiamo una funzione intera max che calcoli il massimo tra due interi:

int max( int i, int j )


{
/*corpo della funzione*/
return( i < j ? j : i ); /* se i < j, allora max restituisce il valore
di i
altrimenti la funzione max restituisce il valore di j*/
}

Una funzione può restituire un qualsiasi tipo di dato (nell'es. un intero) tranne un array.
Quando non viene scritto il tipo di dato il compilatore assume che sia un intero.
I parametri servono per ricevere i valori degli argomenti quando la funzione viene
richiamata.
L'elenco dei parametri può anche non contenere niente, ma le parentesi tonde devono esserci
in ogni caso.

Ogni funzione può essere chiamata all'interno del programma, usando:

• l'identificatore ( nome della funzione ) dato durante la definizione;

• i parametri attuali, ovvero gli elementi che istanziano i parametri.

Regole di visibilità
Il codice di una funzione non è visibile al resto del programma. Le variabili definte al suo
interno sono locali, esse nascono nel momento in cui si entra nella funzione, e vengono
distrutte all'uscita della funzione.
Tutte le funzioni hanno lo stesso livello di visibilità, per cui non può essere definita una
funzione all'interno di un'altra funzione.

Una funzione che chiama se stessa viene denominata ricorsiva.

Il modello a run-time:

• Creazione di una nuova attivazione (istanza) della funzione;

• allocazione della memoria per i parametri e per le variabili locali;

• passaggio dei parametri;


• trasferimento del controllo dalla funzione chiamante alla funzione
chiamata;

• esecuzione del codice della funzione chiamata;

• Al momento della chiamata di una funzione si crea un nuovo


ambiente.
Gli argomenti delle funzioni

Le funzioni accettano argomenti attraverso una lista di variabili, dette parametri formali,
atte a ricevere i valori quando la funzione viene chiamata.
Tali parametri vengono scritti tra parantesi dopo la dichiarazione della funzione.

Quando la funzione viene chiamata agli argomenti vengono assegnati i valori ( parametri
attuali ), ovviamente vi deve essere
coerenza tra i tipi dei parametri formali e gli argomenti.

I parametri formali non sono altro che variabili locali della funzione, quindi vengono create
all'ingresso della funzione e vengono distrutti
all'uscita.

Modalità di passaggio degli argomenti

1. Passaggio per valore

2. Passaggio per indirizzo

Con il primo metodo viene copiato il valore dell'argomento nel


parametro formale; con tale
metodo non è possibile modificare l'argomento passato se viene
effettuato qualche cambiamento sul parametro.
Con il secondo metodo al parametro viene assegnato l'indirizzo
dell'argomento, e le modifiche
apportate sul parametro si ripercuotono anche sull'argomento.

Solitamente viene utilizzato un passaggio per valori degli argomenti. E' comunque possibile
creare una chiamata per indirizzo passando alla funzione un puntatore a un argomento,
invece che l'argomento stesso.

Array passati come parametri

Poiché un array in C è un puntatore costante che punta ad un’area di memoria pre-allocata di


dimensione prefissata , il nome dell’array:

• non rappresenta l’intero array;

• è un alias per il suo indirizzo iniziale


int V[16]
V = &V[0] = indirizzo

Quindi passando un array come parametro di una funzione:

• non si passa l’intero array


• si passa solo per valore il suo indirizzo iniziale

Per l’utente è come se l’array fosse passato per riferimento. Vediamo un esempio:
void funzione (int W[dim]); /*parametro dichiarato come un array di interi
dimensionato */
void funzione (int *W);/*parametro dichiarato come puntatore */
void funzione (int W[]);/*parametro dichiarato come un array non dimensionato
*/

void main()
{
int V[10];
...
funzione (W)
...
}

Esempio:
int lunghezza(char s[])
{
int lung = 0;
for (;s[lung]!=‘\0’;lung++);
return lung;
}

int lunghezza(char *s)


{
int lung = 0;
for (;s[lung]!=‘\0’;lung++);
return lung;
}
Istruzione return

Grazie all'istruzione return si possono fare le seguenti operazioni:

- forzare la fine immediata della funzione, tornando al programma


chiamante
- restituire un valore al programma chiamante

• La fine di una funzione può avvenire in due casi:

• quando viene eseguita l'ultima istruzione della funzione, e


l'esecuzione torna nel punto in cui la funzione era stata chiamata;

• utilizzando l'istruzione return che dà la possibilità di restituire un


valore e rendere il codice di uscita da una funzione più efficiente.

• Le funzioni possono essere:

• un tipo predefinito

• un tipo derivato

• un tipo definito dall’utente

• void.

Solo nei primi tre casi la funzione restituisce un valore


corrispondente al tipo definito, nel quarto caso invece la funzione
non restituirà alcun valore.

Il tipo restituito non puo’ essere nè un vettore nè una funzione, ma


un puntatore ad un vettore o ad una funzione.

Una funzione che non restitusce un valore (procedura) dovrebbe


essere dichiarata di tipo void.

Una funzione per la quale non e’ specificato il tipo restituito


restituisce un valore di tipo int.
La ricorsione

Una funzione matematica è definita per ricorsione (o per induzione) quando è espressa in
termini di se stessa.
Si consideri:

f (n) = 1 se n = 0

f (n) = n * f(n-1) se n > 0

La base teorica è il principio di induzione:

• se una proprietà vale per un certo naturale n=n0;

• e si può dimostrare che, assumendola valida per n, essa è valida


anche per n+1,
allora la proprietà vale n ? n0

In tal modo si specifica la funzione definendo:

• quanto vale in un caso base;

• come si può ricondurre il generico caso di grado n a uno o più casi


più semplici (di grado < n).

La ricorsione nella programmazione


Una funzione C è una astrazione di funzione matematica.
Ogni funzione (ad eccezione della funzione main) può sempre assumere il ruolo di funzione
chiamante e funzione chiamata di ogni altra funzione.
Come caso particolare si ha la funzione ricorsiva, ovvero una funzione che richiama se
stessa.

ESEMPI

• Calcolare il Fattoriale di un numero

Specifica della funzione


Se il numero n su cui calcolare il fattoriale vale 0, restituisci 1;
altrimenti, calcola il fattoriale di n-1, e restituisci tale valore
moltiplicato per n

Codice
unsigned long int fattoriale(int n)
{
if (n ==0)
return 1;
else
return n * fattoriale(n-1);
}
• Calcolare la somma dei primi N numeri positivi

Specifica iterativa
ripeti per N volte l’operazione elementare sum = sum + i

Specifica ricorsiva
Considera la somma (1+2+3+...+(N-1)+N) come composta di due
termini, (1+2+3+...+(N-1)), e N il secondo termine è un valore
singolo, il primo non è altro che il risultato dello stesso problema
in un caso più semplice è facile identificare un caso base: la
somma fino a 1 vale 1.

Codice
int somma (int n);
{
if (n == 1)
return 1;
else
return n+somma(n-1)
}

OSSERVAZIONI:

• l’approccio iterativo richiede di vedere la soluzione del problema


'tutta insieme' in termini di mosse elementari;

• l’approccio ricorsivo richiede invece solo di esprimere il problema


in termini dello stesso problema in casi più semplici, più qualche
mossa elementare;

• inoltre, deve essere identificato un caso banale che non richiede


tale decomposizione (per terminare).

• Calcolare l’ennesimo numero di Fibonacci

Spescifica della funzione


Se il numero n vale 0, restituisci 0; se n vale 1, restituisci 1;
altrimenti, calcola l’ (N-1)-esimo e l’ (N-2)-esimo numero di
Fibonacci, e restituisci la somma di tali valori.

Codice
unsigned long int fib(int n)
{
if (n < 2)
return n;
else
return fib(n-1) + fib(n-2);
}
NOTA:
questo è un esempio di ricorsione non lineare (la definizione della
funzione si basa su più chiamate ricorsive).
I due casi precedenti erano invece esempi di ricorsione lineare
(una sola chiamata ricorsiva).

• Calcolare il Massimo Comune Divisore fra due numeri.

Specifica ricorsiva
MCD(m,n) = n se n = m

MCD(m,n) = MCD(m-n, n) se m > n

MCD(m,n) = MCD(m, n-m) se m < n

Codice
int MCD(int m, int n)
{
if (n == m)
return n;
if (m < n)
return MCD(m-n,n);
return MCD(m,n-m)
}

Ad esempio:
MCD ( 36, 15 )
= MCD (21, 15) = MCD (6, 15)
= MCD (6, 9) = MCD (6, 3)
= MCD (3, 3) = 3

NOTA:
il risultato viene sintetizzato via via che le chiamate ricorsive si
succedono
Dichiarazione dei parametri

In una funzione la dichiarazione classica dei parametri è fatta tra parentesi tonde subito dopo
la funzione, ma senza definire all'interno di queste anche i relativi tipi.
Essi vengono invece definiti subito dopo la chiusura della parentesi tonda e prima di aprire la
parentesi graffa della descrizione del codice della funzione.

Ma l'approccio più usato nell'elencazione dei parametri della funzione è quella di specificare i
tipi nella dichirazione dei parametri tra le parentesi tonde.

Entrambi gli approcci, comunque vanno bene.


Il secondo approccio, quello che s'incontra nella maggior parte dei casi, è il seguente:

tipo nome_funzione (tipo parametro1,tipo parametro2,..tipo parametron)


{
codice della funzione
}

Strutture, unioni e tipi definiti dall'utente


Strutture

La struttura è un raggruppamento di variabili a cui si fa riferimento usando un unico nome.


La struttura C e’ equivalente al record PASCAL.

Una definizione di una struttura permette di creare un modello che puo’ essere usato per
attivare delle variabili, dette membri del tipo struttura.

Per dichiarare una struttura si usa la parola chiave struct, e si procede in tal modo:

struct <nome_tipo_struttura>
{
<tipo> <nome_variabile>;
<tipo> <nome_variabile>;
} <nomi_variabili>;
Con tale dichiarazione si è creata la variabile associata alla struttura (o più di una) grazie al
nome della variabile scritta dopo la chiusura della parentesi graffa.
Se dopo la chiusura di tale parentesi non si scrive nulla, vuol dire che la struttura è stata solo
dichiarata ed occorre ancora definire la variabile attraverso un'altra riga di comando nella
dichiarazione delle variabili, in tal modo:
struct <nome_tipo_struttura><nome_varibile>

Vediamo due esempi che utilizzano entrambi i metodi per definire le variabili della struttura
dichiarata:
struct struttura {
int campo1;
float campo2;
} S,T;

struct struttura {
int campo1;
int campo2;
};
struct struttura S,T

Per accedere ai membri, o campi,della struttura si utilizza l'operatore punto ( . ), ovvero:


<nome_var_struttura>.<nome_campo>

Ad esempio:
S.campo1 = 10;

Per poter fare un'assegnazione alla variabile di tipo struttura si scrive in tal modo:
<nome_var_struttura>=<nome_var_struttura>

Ad esempio:
S = T;

Array di struttura
Gli array di strutture vengono spesso utilizzati e si definiscono nel seguente modo ( è chiaro
che deve essere prima dichiarata la struttura):
struct <nome_tipo_struttura> <nome_var_struttura>[<dimensione>];
Ad esempio:
struct struttura A[100];

Strutture come argomenti di funzione


Una struttura può anche essere utilizzata nel passaggio degli argomenti di una funzione
sia considerando solo dei membri della struttura, sia considerando l'intera struttura.
Nel primo caso, quando si passa cioè a una funzione un membro di una struttura la funzione
riceverà solo il valore di quel campo della struttura. E' possibile passare anche solo l'indirizzo
del membro usando l'operatore &.
Ad esempio:
funzione(T.campo1);

funzione(&S.campo2); /*passaggio per indirizzo*/

Nel caso in cui invece si passa l'intera struttura alla funzione, viene usata la chiamata per
valore.

Esempio di un programma C che utilizza le strutture:


Una rubrica telefonica
# include <stdio.h>
# include <stdlib.h>
# define DIM 100
struct ind {
char nome[20];
char tel[12];
}Info[DIM];
void iniz_lista, inser(void), canc(void), elenca(void)
int menu(void), elem_lib(void)
void main (void)
{
char scelta; iniz_lista();
for (;;)
{
scelta = menu();
switch (scelta)
{
case 1:inser();break;
case 2:canc();break;
case 3:elenca();break;
case 4:exit(0);
}
}
}
void iniz_lista(void)
{
register int i;
for (i=0; i4);
return c;
}

elem_lib(void)
{
register int t;
for (t=0; t=0 && slot < DIM) Info[slot].nome[0]=‘\0’;
}
void elenca(void)
{
register int t;
for (t=0; t<DIM; ++t)
{
if (Info[t].nome[0])
{
printf("%s\n" Info[slot].nome);
printf("%s\n" Info[slot].tel);
}
}
}

Puntatori a strutture

Un puntatore a strutture viene dichiarato ponendo un asterisco prima del nome di una
variabile struttura.

Gli utilizzi dei puntatori a strutture sono i seguenti:

• generazione di parametri per la chiamata per indirizzo a funzioni;

• creazione di liste concatenate e di strutture di dati dinamiche.

Noi consideriamo per ora solo il primo utilizzo.

Vediamo un esempio:
struct tm
{ int ore;
int min;
int sec;
};

struct tm *punt /*dichiarazione del puntatore a struttura*/

Si usa l'operatore freccia, invece dell'operatore punto, per accedere a un membro di una
struttura, tramite un puntatore alla struttura stessa.
Quindi l’operatore freccia -> consente l’accesso ad un campo di una struttura attraverso il
puntatore alla variabile struttura.
Ad esempio:
punt->ore = 4;

Considerando la struttura definita nell'esempio sopra esposto, vediamo adesso una porzione
di programma:
void aggiorna(struct tm *t)
{
t-> sec++;
if (t->sec == 60) {t->sec=0; t->min++;}
if (t->min == 60) {t->min=0; t->ore++;}
if (t->ore == 24) t->ore=0;
ritardo();
}

void main(void)
{
struct tm tempo;
tempo.ore=0;tempo.min=0;tempo.sec=0;
for (;;){aggiorna(&tempo) mostra(&tempo);}
}

Campi di bit

Il C consente di accedere ai singoli bit che si trovano all’interno dei byte.

Il metodo utilizzato dal C per accedere a singoli bit e’ un particolare tipo di struttura in cui
viene definita la lunghezza in bit di ogni elemento.
Quindi un campo bit è un membro di struttura la cui forma generale è la seguente:

struct <nome_tipo_struttura>{
<tipo> <nome_1>: <lunghezza>
...
<tipo> <nome_1>: <lunghezza>
} <elenco_variabili>

Un campo bit può essere dichiarato:

• int

• unsigned

• signed

I campi bit sono molto usati per analizzare gli input provenienti da dispositivi hardware.
Unioni

Una union e’ una locazione di memoria che viene usata da piu’ variabili che possono essere
anche di tipo differente.

La definizione di un unione e’ simile a quella di una struttura:

union <nome_tipo_unione> {
<tipo> <nome_variabile>;
<tipo> <nome_variabile>;
} <nomi_variabili>;

Oppure la dichiarazione delle variabili può essere fatta in un passo successivo in tal modo:

union <nome_tipo_unione> {
<tipo> <nome_variabile>;
<tipo> <nome_variabile>;
}

union <nome_tipo_unione> <nomi_variabili>;

Quando viene dichiarata un unione il compilatore alloca lo spazo sufficiente ad allocare la piu’
grossa delle varibili che la costituiscono.

Per accedere a un membro di una union si usa la sintassi solita delle strutture, cioè usando
l'operatore punto.
Si usa l'operatore freccia se l'accesso all'unione avviene attraverso il puntatore.

Tipi enumerativi

Un tipo enumerativo, introdotto dallo standard ANSI e’ un insieme di costanti intere dotate di
nome, che specifica quali sono i valori ammissibili.

La parola chiave è enum e la sintassi usata è la seguente:

enum <tipo_enumerativo>
{<lista_valori>}<lista_variabili>
Nell'enumerazione ogni simbolo corrisponde a un valore intero.
A ogni simbolo della lista corrisponde un valore maggiore di uno rispetto il simbolo
precedente.
Solitamente il primo simbolo dell'enumerazione corrisponde a 0.

Vediamo degli esempi di dichiarazioni di variabili di tipo enumerativo:


enum giorni {lunedi, martedi, mercoledi,
giovedi,venerdi,sabato, domenica};

enum giorni G;

enum giorni {lunedi, sabato=6, domenica=7};

Sizeof

Attraverso l'operatore sizeof è possibile calcolare le dimensioni di una variabile.

Tale operatore, che viene eseguito al momento della compilazione, può essere utilizzato anche
per le strutture e le unioni.

Esempio
char n;
int cont;

printf ("%d",sizeof(nome));

printf ("%d",sizeof(cont));

Avendo una struttura :


struct struttura{
char m;
float a;
} pippo;

e considerando che in C il char ha dimensioni 1 bit, e il float 4 bit,


sizeof (pippo);

risulterà almeno di lunghezza 7.

Allo stesso modo si può applicare il sizeof anche alle union.


Typedef

In C esiste il typedef che ha due possibilità di utilizzo:

• permette di definire esplicitamente nuovi nomi per i tipi di dati


tramite la parola chiave typedef.

Ciò consente di rendere il codice:

• piu’ portabile;

• piu’ leggibile.

In tal modo non si crea un nuovo tipo, ma si definisce un nuovo


tipo, ma si definisce un nuovo nome per un tipo già esistente.
La sintassi è la seguente:

typedef <tipo> <nome>

Esempio:
typedef float tipo;
tipo variabile;

• permette, inoltre, di definire assegnare un nome ad un tipo


struttura, unione ed enumerazione.

Esempio:
typedef struct tm
{ int ore;
int min;
int sec
}time;
/*time è il nome di un tipo */

struct tm var;
time var;

struct tm *punt;
time *punt;
da

http://www.diit.unict.it/users/michele/didattica/fondamenti/fondamenti/indice.html

http://www.diit.unict.it/users/michele/didattica/fondamenti/fondamenti/lingC.html

Potrebbero piacerti anche