Upload
others
View
3
Download
0
Embed Size (px)
Citation preview
1
Una procedura permette di– dare un nome a una istruzione– rendendola parametrica
– non denota un valore, quindi non c’è tipo di ritorno →→→→ void
void p(int x) {
x = x * 2;
printf(“%d”, x);
}
PROCEDURE
2
Una procedura è un componente software che cattura l’idea di “macro-istruzione”
– molti possibili parametri, che possono anche essere modificati mentre nelle funzioni normalmente non devono essere modificati
– nessun “valore di uscita” esplicito
PROCEDURE COME SERVITORI
Come una funzione,una procedura è un servitore� passivo� che serve un cliente per volta� che può trasformarsi in cliente invocando se stessa o altre
procedure
• In C, una procedura ha la stessa struttura di una funzione, salvo il tipo di ritorno che è void
3
L’istruzione return provoca solo la restituzione del controllo al cliente e non è seguita da una espressione da restituire -> non è necessaria se la procedura termina “spontaneamente” a fine blocco
Nel caso di una procedura, non esistendo valore di ritorno, cliente e servitore comunicano solo:�mediante parametri�mediante aree dati globali
Occorre il passaggio per riferimento per fare cambiamenti permanenti ai dati del cliente
PROCEDURE
4
In generale, un parametro può esseretrasferito dal cliente al servitore:
• per valore o copia (by value)si trasferisce il valore del parametro attuale
• per riferimento (by reference)si trasferisce un riferimento al parametro attuale
PASSAGGIO DEI PARAMETRI
5
Perché il passaggio per valore non basta?
Problema: scrivere una procedura che scambi i valori di due variabili intere
Specifica:Dette A e B le due variabili, ci si può appoggiare a una variabile ausiliaria T, e svolgere lo scambio in tre fasi
ESEMPIO
AB
T
AB
T
10 7
7
7
1010
1
2
AB
T7
1010
3
6
Supponendo di utilizzare, senza preoccuparsi, il passaggio per valore usato finora, la codifica potrebbe essere espressa come segue:
void scambia(int a, int b) {
int t;
t = a; a = b; b = t;
return; /* può essere omessa */
}
ESEMPIO
7
Il cliente invocherebbe quindi la procedura così:
int main(){
int y = 5, x = 33;
scambia(x, y);
/* ora dovrebbe essere x=5, y=33 ... MA NON È VERO
*/}
Perché non funziona?
ESEMPIO
8
• La procedura ha effettivamente scambiato i valori di A e B al suo interno (in C nel suo record di attivazione)
• ma questa modifica non si è propagata al cliente, perché sono state scambiate le copie locali alla procedura, non gli originali
• al termine della procedura, le sue variabili locali sono state distrutte → nulla è rimasto del lavoro svolto dalla procedura
X 33
Y 5
A 5
B 33
ESEMPIO
9
cliente servitore
x 33copia
a33
valori (copiati) di x e y
y 5 b5
PASSAGGIO PER VALORE
Ogni azione fatta su a e b è strettamente locale al servitore. Quindi a e b vengono scambiati ma quando il servitore termina, tutto scompare
10
Il C adotta sempre il passaggio per valore
– le variabili del cliente e del servitore sono disaccoppiate
– ma non consente di scrivere componenti software il cui scopo sia diverso dal calcolo di una
espressione
– per superare questo limite occorre il passaggio per riferimento (by reference)
PASSAGGIO DEI PARAMETRI IN C
11
Il passaggio per riferimento (by reference)
�NON trasferisce una copia del valore del parametro attuale
�ma un riferimento al parametro, in modo da dare al servitore accesso diretto al parametro in possesso del cliente
� il servitore, quindi, accede direttamente al dato del cliente e può modificarlo
PASSAGGIO PER RIFERIMENTO
12
Il linguaggio C NON supporta direttamente il passaggio per riferimento
• è una grave mancanza
• viene fornito indirettamente solo per alcuni tipi di dato
• occorre quindi costruirlo quando serve
PASSAGGIO DEI PARAMETRI IN C
13
cliente servitore
riferimentoaαααα
riferimenti a x e y(indirizzi)
ααααx 33
y 5 bββββββββ riferimento
PASSAGGIO PER RIFERIMENTO
Ogni azione fatta su a e bin realtà è fatta su x e y
nell’environment del cliente
Si trasferisce un riferimento ai parametri attuali (cioè i loro indirizzi)
14
Il C non fornisce direttamente un modo per attivare il passaggio per riferimento -> a volte occorre costruirselo
• Poiché passare un parametro per riferimento comporta la capacità di manipolare indirizzi di variabili…
• … gestire il passaggio per riferimento implica la capacità di accedere, direttamente o indirettamente, agli indirizzidelle variabili
REALIZZARE IL PASSAGGIO PER RIFERIMENTO IN C
È possibile costruirlo? Come?
15
In particolare occorre essere capaci di:• ricavare l’indirizzo di una variabile
• dereferenziare un indirizzo di variabile, ossia “recuperare” il valore dato l’indirizzo della variabile
Nei linguaggi che offrono direttamente il passaggio per riferimento, questi passi sono effettuati in modo trasparente all’utente
In C il programmatore deve conoscere gli indirizzidelle variabili e quindi accedere alla macchina sottostante
REALIZZARE IL PASSAGGIO PER RIFERIMENTO IN C
16
Il C offre a tale scopo due operatori, checonsentono di:
• ricavare l’indirizzo di una variabileoperatore estrazione di indirizzo &
• dereferenziare un indirizzo di variabile, denotando la variabile (e il valore contenuto in quell’indirizzo)
operatore di dereferenziamento *
INDIRIZZAMENTO E DEREFERENCING
17
Se x è una variabile,&x denota l’indirizzo in memoria di tale variabile:
&x ≡≡≡≡ αααα
Se α è l’indirizzo di una variabile,*αααα denota tale variabile:
x ≡≡≡≡ *αααα
INDIRIZZAMENTO E DEREFERENCING
18
&x ≡≡≡≡ ααααx ≡≡≡≡ *αααα
ααααx
Livello di astrazione della macchina fisica sottostante
Livello di astrazione del linguaggio di alto livello
INDIRIZZAMENTO E DEREFERENCING
19
Un puntatore è il costrutto linguistico introdotto dal C (e da altri linguaggi) come forma di
accesso alla macchina sottostante e in particolare agli indirizzi di variabili
• Un tipo puntatore a T è un tipo che denota l’indirizzo di memoria di una variabile di tipo T
• Un puntatore a T è una variabile di “tipo puntatore a T”
che può contenere l’indirizzo di una variabile di tipo T
PUNTATORI
20
Definizione di una variabile puntatore:
<tipo> * <nomevariabile> ;
Esempi:
int *p;
int* p;
int * p;
Queste tre forme sono equivalenti e definiscono p come “puntatore a intero”
PUNTATORI
21
• il cliente deve passare esplicitamente gli indirizzi
• il servitore deve prevedere esplicitamente dei
puntatori come parametri formali
PASSAGGIO PER RIFERIMENTO IN C
void scambia(int* a, int* b) {
int t;
t = *a; *a = *b; *b = t;
}
int main(){
int y=5, x=33;
scambia(&x, &y);
}
22
Caso del passaggio per valore:
ESEMPIO: RECORD DI ATTIVAZIONE
main
scambia(3,5)
x 33y 5
RA DLa 33 5b 5 33t 33
Copia di x e y
Non modificati dalla procedura
23
Caso del passaggio per riferimento:
ESEMPIO: RECORD DI ATTIVAZIONE
main
scambia(3,5)
x 33 5y 5 33
RA DLa ααααb ββββt 33
Indirizzo di x e y
Modificati dalla procedura
αααα
ββββ
24
Quando un puntatore è usato per realizzare il passaggio per riferimento, la funzione non dovrebbe mai alterare il valore del puntatore
Quindi, se a e b sono due puntatori:
*a = *b SI
a = b NO
In generale una funzione PUÒ modificare un puntatore, ma non è opportuno che lo faccia se esso realizza un
passaggio per riferimento
OSSERVAZIONE
25
• Un puntatore è una variabile destinata a contenere
l’indirizzo di un’altra variabile
• Vincolo di tipo: un puntatore a T può contenere solo l’indirizzo di variabili di tipo T
Esempio:
int x = 8;
int* p;
p = &x;
PUNTATORI
Da questo momento, *p e x sono due modi
alternativi per denotare la stessa variabile
px
αααα8αααα
26
int x = 8;
int* p = &x;
*p = 31;
x--;
PUNTATORI
px
αααα8αααα
xαααα8
xαααα8 31p αααα
xαααα31 30p αααα
27
Un puntatore non è legato per semprealla stessa variabile; può essere modificato
int x = 8, y = 66;
int *p = &x;
(*p)++;
p = &y;
(*p)--;
PUNTATORI
px αααα
8 9αααα
px αααα
9αααα
yββββ
66 65
Le parentesi sono necessarie? Che cosa identifica la scrittura *p-- ?
28
Un puntatore a T può contenere solo l’indirizzo di variabili di tipo T: puntatori a tipi diversi sono incompatibili tra loro
Esempio:int x=8, *p; float *q;p = &x; /* OK */q = p; /* NO! */
MOTIVO: il tipo del puntatore serve per dedurre iltipo dell’oggetto puntato, che è una informazioneindispensabile per effettuare il dereferencing
PUNTATORI
29
void scambia(int* pa, int* pb) {
int t;
t = *pa; *pa = *pb; *pb = t;
}
int main(){
int y = 5, x = 33;
int *py = &y, *px = &x;
scambia(px, py);}
PUNTATORI
Variazione dall’esempio precedente: i puntatori sono memorizzati in px e py prima di passarli alla procedura
30
Il record di attivazione si modifica come segue
ESEMPIO: RECORD DI ATTIVAZIONE
main
scambia(3,5)
x 33 5y 5 33
RA DLpa ααααpb ββββt 33
Copia di px e py
Modificati dalla procedura
αααα
ββββpx ααααpy ββββ
31
Una procedura può anche comunicare con il cliente mediante aree dati globali: ad esempio, variabili globali
Le variabili globali in C:• sono allocate nell’area dati globale (fuori da ogni funzione)
• esistono prima della chiamata del main
• sono visibili, previa dichiarazione extern, in tutti i file dell’applicazione
• sono inizializzate automaticamente a 0 salvo diversa indicazione
• possono essere nascoste in una funzione da una variabile locale omonima
COMUNICAZIONE TRAMITE ENVIRONMENT GLOBALE
32
int quoziente, int resto;
void dividi(int x, int y) {
resto = x % y; quoziente = x/y;
}
int main(){
dividi(33, 6);
printf(“%d%d”, quoziente,resto);
}
Esempio: Divisione intera x/y con calcolo di quoziente e resto. Occorre calcolare due valori che supponiamo di mettere in due variabili globali
Il risultato è disponibile per il cliente nelle variabili globali quoziente e resto
ESEMPIO
variabili globali quoziente e resto visibili in tutti i blocchi
33
void dividi(int x, int y, int* quoziente,
int* resto) {
*resto = x%y; *quoziente = x/y;
}
int main(){
int k = 33, h = 6, quoz, rest;
int *pq = &quoz, *pr = &rest;
dividi(33, 6, pq, pr);
printf(“%d%d”, quoz, rest);
}
Esempio: Con il passaggio dei parametri per indirizzo avremmo il seguente codice
SOLUZIONE ALTERNATIVA
• Sono considerate “applicazioni di piccola dimensione”, applicazioni con qualche migliaio di linee di codice
• Un’applicazione anche “di piccola dimensione” non può essere sviluppata in un unico file �modularità, riutilizzo, leggibilità
• Deve necessariamente essere strutturata su più file sorgente– Compilabili separatamente
– Da collegare insieme successivamente per costruire l’applicazione
Una Parentesi: PROGETTI SU PIÙ FILE SORGENTE
La dichiarazione di una funzione è costituita dalla sola interfaccia, senza corpo (sostituito da un ;)
<dichiarazione-di-funzione> ::=
<tipoValore> <nome>(<parametri>) ;
• Per usare una funzione non occorre conoscere tutta la definizione
• È sufficiente conoscerne la dichiarazione ovvero la specifica del contratto di servizio
DICHIARAZIONE di FUNZIONE
• La dichiarazione specifica:– nome della funzione– numero e tipo dei parametri (non
necessariamente il nome)– tipo del risultato
Nota: il nome dei parametri non è necessario, se c’è viene ignorato…
� Avrebbe significato solo nell’ambiente di esecuzione (vedi record di attivazione) della funzione, che però al momento non esiste (non essendoci la definizione)
DICHIARAZIONE di FUNZIONE
• Definizione: dice come è fatto il componente– costituisce l’effettiva realizzazione del
componente– NON può essere DUPLICATA
– compilazione di una definizione genera codice oggetto corrispondente alla funzione
• La dichiarazione di una funzione costituisce solo una specifica delle proprietà del componente– Può essere duplicata senza problemi
DICHIARAZIONE vs. DEFINIZIONE
• main() può essere scritto dove si vuole nel file– viene invocato dal sistema operativo, che lo
identifica sulla base del nome
• Una funzione deve rispettare una regola fondamentale di visibilità– Prima che qualcuno possa invocarla, la funzione
deve essere stata dichiarata (va bene anche definizione – contiene una dichiarazione)
– ...altrimenti ���� errore di compilazione!
FUNZIONI e FILE
int fact(int);
int main()
{
int y = fact(3);
printf(“%d”, y);
return 0;
}
int fact(int n)
{
return (n<=1) ? 1 : n * fact(n-1);
}
Dichiarazione (prototipo):
deve precedere l’uso
Uso (invocazione)
Definizione
ESEMPIO: File Singolo
• Per strutturare un’applicazione su più file sorgente, occorre che ogni file possa essere compilato separatamente dagli altri– Successivamente avverrà il collegamento
• Affinché un file possa essere compilato, tutte le funzioni usate devono essere almeno dichiarate prima dell’uso– Non necessariamente definite
PROGETTI su PIÙ FILE
int fact(int);
int main()
{
int y = fact(3);
printf(“%d”, y);
return 0;
}
int fact(int n)
{
return (n<=1) ? 1 :
n * fact(n-1);
}
2. Compilare i singoli file che costituiscono l’applicazione
– File sorgente: estensione .c– File oggetto: estensione .o oppure .obj
f1.c
f2.c
f3.c
f1.obj
f2.obj
f3.obj
compilatore
compilatore
compilatore
COMPILAZIONE di una APPLICAZIONE
2. Collegare i file oggetto fra loro e con lelibrerie di sistema
– File oggetto: estensione .o oppure .obj– File eseguibile: estensione .exe o nessuna
prog.exe
f1.obj
f2.obj
f3.obj
linker
COMPILAZIONE di una APPLICAZIONE
Perché la produzione dell’eseguibile vada a buon fine:
• ogni funzione deve essere definita una e una
sola volta in uno e uno solo dei file sorgente– se la definizione manca, si ha errore di linking
• ogni cliente che usi una funzione deve incorporare la dichiarazione opportuna– se la dichiarazione manca, si ha errore di
compilazione nel file del cliente
RIASSUMENDO…
• Ogni chiamante deve contenere le dichiarazioni delle funzioni che utilizza
Per automatizzare la gestione delle dichiarazioni, si introduce il concetto di header file (file di intestazione)
• Scopo: evitare ai clienti di dover trascrivere riga per riga le dichiarazioni necessarie– il progettista predispone un header file contenente
tutte le dichiarazioni relative alle funzioni definite nel suo componente software (o modulo)
– i clienti potranno semplicemente includere tale header tramite direttiva #include
PROGETTI COMPLESSI…
Il file di intestazione (header)
• ha estensione .h
• ha (per convenzione) nome uguale al file .c di cui fornisce le dichiarazioni
Esempio:• se la funzione func1 è definita nel file file2c.c• il corrispondente header file, che i clienti potranno
includere per usare la funzione func1, dovrebbe chiamarsi file2c.h
HEADER FILE
Due formati:
1) #include <libreria.h>
include normalmente header di libreria di sistemamacchina runtime del C sa dove cercare file header (all’interno di un elenco di directory predefinite)
2) #include “miofile.h”
include normalmente un header di “propria produzione”
HEADER FILE
• Attenzione: un header file dovrebbe contenere solo dichiarazioni e non definizioni (sia di funzioni che di variabili)– Possibile problema di una definizione compilata più volte,
generando poi anche errori di linking– E se servono variabili globali utilizzate da più file
sorgenti?• Clausola extern (vedi lucidi seguenti)
Suddivisione su due file separatifloat fahrToCelsius(float);
int main() { float c =
fahrToCelsius(86);}
File main.c
float fahrToCelsius(float
f) {
return 5.0/9 * (f-32);
}
File f2c.c
Esempio: CONVERSIONE °F/°C
Si può introdurre un file header per includere automaticamente la dichiarazione
#include "f2c.h"
int main() { float c = fahrToCelsius(86);}
File main.c
float fahrToCelsius(float);
File f2c.h (header)
Esempio: CONVERSIONE °F/°C
Struttura finale dei file dell’applicazione– Un file main.c contenente il main
– Un file f2c.c contenente la funzione di conversione
– Un file header f2c.h contenente la dichiarazione della funzione di conversione
• Incluso da main.c
Esempio: CONVERSIONE °F/°C
In C, ogni funzione ha il suo ambiente locale che comprende i parametri e le variabili definite localmente alla funzione
AMBIENTE LOCALE E GLOBALE
Esiste però anche un ambiente globale:quello dove tutte le funzioni sono definiteQui si possono anche definire variabili, dettevariabili globali
La denominazione "globale" deriva dal fatto che l'environment di definizione di queste variabili non coincide con quello di nessuna funzione (neppure con quello del main())
• Una variabile globale è dunque definitafuori da qualunque funzione(“a livello esterno”)
• tempo di vita = intero programma
• scope = il file in cui è dichiarata dal punto in cui è scritta in avanti
VARIABILI GLOBALI
int trentadue = 32;
float fahrToCelsius( float F ){
float temp = 5.0 / 9;
return temp * ( F - trentadue );
}
Anche per le variabili globali, come per le funzioni, si distingue fra dichiarazione edefinizione
– al solito, la dichiarazione esprime proprietà associate al simbolo,ma non genera un solo byte di codice o di memoria allocata
– la definizione invece implica ancheallocazione di memoria, e funge contemporaneamente da dichiarazione
DICHIARAZIONI e DEFINIZIONI
int trentadue = 32;
float fahrToCelsius(float);
int main(void) {
float c = fahrToCelsius(86);
}
float fahrToCelsius(float f) {
return 5.0/9 * (f-trentadue);
}
Definizione (e inizializ-zazione) della variabile
globale
Uso della variabile globale
ESEMPIO
Come distinguere la dichiarazione di unavariabile globale dalla sua definizione?
�nelle funzioni è facile perché la dichiarazione ha un ";" al posto del corpo {….}
�ma qui non c’è l’analogo …..
si usa l'apposita parola chiave extern
DICHIARAZIONI e DEFINIZIONI
• int trentadue = 10;
è una definizione (con inizializzazione)
• extern int trentadue;
è una dichiarazione (la variabile sarà definita in un altro file sorgente appartenente al progetto)
extern int trentadue;
float fahrToCelsius(float f) {
return 5.0/9 * (f-trentadue);
}
int main(void) {
float c = fahrToCelsius(86);
}
int trentadue = 32;
Dichiarazionevariabile globale
Uso della var globale
Definizione dellavariabile globale
ESEMPIO (caso particolare con un solo file sorgente)
• Il cliente deve incorporare la dichiarazionedella variabile globale che intende usare:extern int trentadue;
• Uno dei file sorgente nel progetto dovrà poi contenere la definizione (ed eventual-mente l’inizializzazione) della variabile globale
int trentadue = 10;
VARIABILI GLOBALI: USO
float fahrToCelsius(float f);
int main(void) { float c = fahrToCelsius(86); }
File main.c
extern int trentadue;
float fahrToCelsius(float f) {
return 5.0/9 * (f-trentadue);
}
File f2c.c
int trentadue = 32;
File 32.c
ESEMPIO su 3 FILE
A che cosa servono le variabili globali?
• per scambiare informazioni fra chiamante e funzione chiamata in modo alternativo al passaggio dei parametri
• per costruire specifici componenti software dotati di stato
VARIABILI GLOBALI
Nel primo caso, le variabili globali:
• sono un mezzo bidirezionale: la funzione può sfruttarle per memorizzare una informazione destinata a sopravviverle (effetto collaterale o side effect)
• ma introducono un accoppiamento fra cliente e servitore che limita la riusabilitàrendendo la funzione stessa dipendente dall'ambiente esterno
� la funzione opera correttamente solo se l'ambiente globale definisce tali variabili con quel preciso nome, tipo e significato
VARIABILI GLOBALI
Si vuole costruire un componente softwarenumeriDispari che fornisca una funzione
int prossimoDispari(void)
che restituisca via via il "successivo" dispari
• Per fare questo, tale componente deve tenere memoria al suo interno dell'ultimo valore fornito
• Dunque, non è una funzione in senso mate-matico, perché, interrogata più volte, dà ogni volta una risposta diversa
Secondo Caso: ESEMPIO
• un file dispari.c che definisca la funzionee una variabile globale che ricordi lo stato
• un file dispari.h che dichiari la funzione
ESEMPIO
int ultimoValore = 0;
int prossimoDispari(void){
return 1 + 2 * ultimoValore++; }
int prossimoDispari(void);
dispari.h
dispari.c
(sfrutta il fatto che i dispari hanno la forma 2k+1)
Il fatto che le variabili globali in C sianopotenzialmente visibili in tutti i file dell’appli-cazione pone dei problemi di protezione:
• Che cosa succede se un componente dell'applicazione altera una variabile globale?
• Nel nostro esempio: cosa succede se qualcuno altera ultimoValore?
AMBIENTE GLOBALE e PROTEZIONE
Potrebbe essere utile avere variabili• globali nel senso di permanenti come
tempo di vita (per poter costruire componenti dotati di stato)...
• … ma anche protette, nel senso che non tutti possano accedervi
VARIABILI STATICHE
AMBIENTE GLOBALE e PROTEZIONE
In C, una variabile può essere dichiaratastatic:
• è permanente come tempo di vita• ma è protetta, in quanto è visibile solo
entro il suo scope di definizione
Nel caso di una variabile globale static, ognitentativo di accedervi da altri file, tramitedichiarazioni extern, sarà impedito dalcompilatore
VARIABILI static
static int ultimoValore = 0;
int prossimoDispari(void){
return 1 + 2 * ultimoValore++;
}
dispari.c
(dispari.h non cambia)
ESEMPIO rivisitato
In che senso la variabile static è "protetta"?• La variabile ultimoValore è ora inaccessibile
dall'esterno di questo file: l’unico modo di accedervi è tramite prossimoDispari()
• Se anche qualcuno, fuori, tentasse di accedere tramite una dichiarazione extern, il linker non troverebbe la variabile
• Se anche un altro file definisse un’altra variabile globale di nome ultimoValore, non ci sarebbe comunque conflitto, perché quella static “non è visibile esternamente”
ESEMPIO rivisitato
Una variabile statica può essere definitaanche dentro a una funzione. Così:• è comunque protetta, in quanto visibile
solo dentro alla funzione (come ogni variabile locale)
• ma è anche permanente, in quanto il suo tempo di vita diventa quello dell’intero programma
Consente di costruire componenti (funzioni)dotati di stato, ma indipendenti dall'esterno
VARIABILI STATICHE dentro a FUNZIONI
int prossimoDispari(void){
static int ultimoValore = 0;return 1 + 2 * ultimoValore++;
}
dispari.c
(dispari.h non cambia)
ESEMPIO rivisitato (2)
Quindi, la parola chiave static
• ha sempre e comunque due effetti
– rende l’oggetto permanente
– rende l’oggetto protetto(invisibile fuori dal suo scope di definizione)
• ma se ne vede sempre uno solo per volta
– una variabile definita in una funzione, che ècomunque protetta, viene resa permanente
– una variabile globale, già di per sé permanente,viene resa protetta
VARIABILI STATICHE