Post on 02-May-2015
1
Il linguaggio C
Puntatori e dintorni
2
Puntatori : idea di base
• In C è possibile conoscere e denotare l’indirizzo della cella di memoria in cui è memorizzata una variabile (il puntatore)• es :int a = 50; /* una var intera */
int * b; /* una var puntatore a interi */
...
b = &a; /* assegna a b l’indirizzo della cella in cui è memorizzata a */
3
Puntatori : idea di base (2)
• In C è possibile conoscere e denotare l’indirizzo della cella di memoria in cui è memorizzata una variabile (il puntatore)• es :int a = 50;
int *b;
…
b = &a;
50350
a è memorizzata nella cella 350
...450
4
Puntatori : idea di base (3)
• nometype * – è il tipo degli indirizzi delle variabili di tipo nometype
• es :int a = 50;
int * b;
…
b = &a;
50350
b è memorizzata nella cella 450 (&b)
...450
tipo dei puntatori a intero
5
Puntatori : idea di base (4)
• Operatore & – denota l’indirizzo della cella di memoria in cui è memorizzata una variabile (il puntatore)
• es :int a = 50;
int *b;
…
b = &a;
50350
Dopo questo assegnamento in b è memorizzato l’indirizzo di a
350450
6
Puntatori : idea di base (5)
• Operatore di dereferenziazione ‘ * ’– è possibile conoscere e/o modificare il contenuto di una variabile manipolando direttemente il suo puntatore
• es :int a = 50;
int *b = &a;
…
*b = *b + 4;
54350
Dopo questo assegnamento in a è memorizzato il valore 50 + 4
350450
Denota la variabilea indirizzo b
7
Puntatori : idea di base (6)• NULL
– costante predefinita (in stdio.h) che denota il puntatore nullo
• È possibile definire puntatori per tutti itipi base e le strutture con (*)– double *a, *b; /* ripetere ‘*’ */– int *a, b, c[4], **d; – struct studente * t1;
• Segnaposto ( %p )– stampa il valore dell’indirizzo in notazione esadecimale
8
Aritmetica dei puntatori
• È possibile scrivere espressioni puntatore usando alcuni degli usuali operatori aritmetici (+, -, --, ++)– int a[3],*p=&a[0];
……IN+12IN+8IN+4IN
a[2]a[1]a[0]
p contiene l’indirizzo IN
9
Aritmetica dei puntatori (2)
• È possibile scrivere espressioni puntatore usando alcuni degli usuali operatori aritmeticiint a[3],*p=&a[0];
p = p+1;
……IN+12IN+8IN+4IN
a[2]a[1]a[0]
p contiene l’indirizzo IN + 4
10
Aritmetica dei puntatori (3)
• È possibile scrivere espressioni puntatore usando alcuni degli usuali operatori aritmeticiint a[3],*p=&a[0];
p = p+1;
p--;
……IN+12IN+8IN+4IN
a[2]a[1]a[0]
p contiene l’indirizzo IN
11
Aritmetica dei puntatori (4)
• È possibile scrivere espressioni puntatore usando alcuni degli usuali operatori aritmetici (+, -, --, ++)int a[3],*p=&a[0];
p = p+1;
p--;
p+=3;
……IN+12IN+8IN+4IN
a[2]a[1]a[0]
p contiene l’indirizzo IN + 12(sizeof(int)==4…..)
12
Aritmetica dei puntatori (5)
• È possibile scrivere espressioni puntatore usando alcuni degli usuali operatori aritmetici (+, -, --, ++)int a[3],*p=&a[0],*q;
p = p+1;
p--;
q = p;
p+=3;
a[0] = p-q;
……IN+12IN+8IN+4IN
a[2]a[1]a[0]
A[0] contiene 3, numero di intmemorizzabili fra p e q
13
Puntatori e array….
• Il nome di un array, è il puntatore (costante) al primo elemento dell’array int a[3],*p=&a[0],*q;
q = a;
……IN+12IN+8IN+4IN
a[2]a[1]a[0]
q contiene l’indirizzo INa == IN
14
Puntatori e array…. (2)
• L’operatore [-] è una abbreviazione ….int a[3],*p=&a[0],*q, tmp;
/* i due stm che seguono sono equivalenti */
tmp = a[2];
tmp = *(a+2);
……a+3a+2a+1a
a[2]a[1]a[0]
15
Puntatori e array…. (3)• L’operatore [–] è una abbreviazione …. e può essere usato con una qualsiasi variabile puntatore
int a[3],*p=a,*q, tmp;
tmp = a[2];
tmp = p[2];
……a+3a+2a+1a
a[2]a[1]a[0]
16
Puntatori e array…. (4)• I seguenti frammenti di codice sono equivalenti :
int a[N],*p=a,*q, tmp;
int sum = 0;
/* versione 1 */
for(i=0;i<N;i++)
sum+= a[i];
/* versione 2 */
for(i=0;i<N;i++)
sum+= *(a+i);
17
Puntatori e array…. (5)• I seguenti frammenti di codice sono equivalenti (segue) :
int a[N],*p=&a[0],*q, tmp;
int sum = 0;
/* versione 3 */
for(i=0;i<N;i++)
sum+= p[i];
/* versione 4 */
for(p=a;p<(a+N);p++)
sum+= *p;
18
Puntatori e array…. (6)• Una riflessione sulle stringhe
– le stringhe sono array di caratteri
char a[7]=“ciao”;– le stringhe costanti possono essere definite anche come
const char * pp=“ciao”;
char * pp=“ciao”;• attenzione! Se a questo punto cercate di modificare un elemento di pp (es. pp[2]=`f`;) avete un errore a run time• mentre a[2]=`f`; è completamente corretto
19
Passaggio di parametri per riferimento
• Tutti i parametri delle funzioni C sono passati per valore – il loro valore viene copiato sullo stack– ogni modifica al parametro nel corpo della funzione non modifica l’originale
• è possibile realizzare passaggi per riferimento utilizzando i puntatori– i passaggi per riferimento permettono di modificare il valore di una variabile nell’ambiente del chiamante
20
Passaggio di parametri per riferimento (2)
• Esempio : la funzione che scambia fra loro i valori di due variabili :– si potrebbe pensare di programmarla come ...
void scambia (int x, int y){
int tmp;
tmp = x;
x = y;
y = tmp;
}
– e poi chiamare scambia(a,b)
21
Passaggio di parametri per riferimento (3)
• Esempio : la funzione che scambia fra loro i valori di due variabili :– si potrebbe pensare di programmarla come ...
void scambia (int x, int y){
int tmp;
tmp = x;
x = y;
y = tmp;
}
– non funziona! Perché lo scambio viene fatto sulle copie
22
Passaggio di parametri per riferimento (3.1)
• Esempio : la funzione che scambia fra loro i valori di due variabili :– esempio di chiamata
int a=4, b=5;
scambia (a,b);
45
45
Framechiamante
&a&b
Framescambia
&x&y
stack
23
Passaggio di parametri per riferimento (3.2)
• Esempio : la funzione che scambia fra loro i valori di due variabili :– alla fine dell’esecuzione di scambia (prima di ritornare al chiamante)
int a=4, b=5;
scambia (a,b);
45
54
Framechiamante
&a&b
Framescambia
&x&y
24
Passaggio di parametri per riferimento (3.3)
• Esempio : la funzione che scambia fra loro i valori di due variabili :– al momento di eseguire la printf() (il frame di scambia non e’ piu’ significativo)
int a=4, b=5;
scambia (a,b);
printf(“%d,%d”,a,b);
45
Framechiamante
&a&b
25
Passaggio di parametri per riferimento (4)
• Esempio : la funzione che scambia fra loro i valori di due variabili :– la versione corretta è ...
void scambia (int* x, int* y){
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
– con chiamata scambia(&a,&b)
26
Passaggio di parametri per riferimento (4.1)
• Versione corretta di scambia ... :– esempio di chiamata
int a=4, b=5;
scambia (&a,&b);
45
&a&b
Framechiamante
&a&b
Framescambia
&x&y
stack
Ogni modifica a *x modificail valore di a nell’ambiente del chiamante
27
Passaggio di parametri per riferimento (3.2)
• Esempio : versione corretta di scambia ...– alla fine dell’esecuzione di scambia (prima di ritornare al chiamante)
int a=4, b=5;
scambia (&a,&b);
54
&a&b
Framechiamante
&a&b
Framescambia
&x&y
28
Passaggio di parametri per riferimento (3.3)
• Esempio : versione corretta di scambia...– al momento di eseguire la printf() (il frame di scambia non e’ piu’ significativo)
int a=4, b=5;
scambia (&a,&b);
printf(“%d,%d”,a,b);
54
Framechiamante
&a&b
29
Passaggio di parametri per riferimento (5)
• ATTENZIONE : gli array sono passati sempre per riferimento perchè quello che si passa è il nome dell’array void assegna (int x[]){
x[0] = 13;
}
– con chiamata int a[10];
assegna(a);
/* qua a[0] vale 13 */
30
Passaggio di parametri per riferimento (6)
• Inoltre : le due scritture void assegna (int x[]){
x[0] = 13;
}
– e void assegna (int* x){
x[0] = 13;
}
– sono del tutto equivalenti– si preferisce usare le prima per leggibilità
31
Passaggio di parametri per riferimento (7)
• Tipicamente le funzioni che lavorano su array hanno un secondo parametro che fornisce la lunghezza int somma (int x[], int l){
int i, s=0;
for(i=0;i<l;i++)
s += x[i];
return s;
}
– somma tutti gli elementi di un array intero di lunghezza l
32
Passaggio di parametri per riferimento (8)
• Per gli array multidimensionali la cosa è più complessa!!!! int somma (int x[][4], int l){
int i, j, s=0;
for(i=0;i<l;i++)
for(j=0;j<4;j++)
s += x[i][j];
return s;
}
– invece di 4 posso usare N costante
33
Passaggio di parametri per riferimento (9)
• Perché dobbiamo specificare l’ampiezza di una seconda dimensione di un array ? – Dipende dalla strategia di memorizzazione per gli array multidimensionali
• es: int a[2][3]={{1,2,3},{4,5,6};
123456
a&a[0][1]&a[0][2]&a[1][0]&a[1][1]&a[1][2]
a[i][j] ha come indirizzo a+i*3+j
100104108112116120
34
Passaggio di parametri per riferimento (10)
• Se non conosco la lunghezza della seconda dimensione il compilatore non riesce a generare codice corretto int somma (int x[][4], int l){
int i, j, s=0;
for(i=0;i<l;i++)
for(j=0;j<4;j++)
s += x[i][j];
return s;
}
35
Passaggio di parametri per riferimento (11)
• C’è un modo migliore di questo di rappresentare gli array multidimensionali in C, lo vediamo più avanti
36
E le strutture ???• Le strutture vengono sempre passate per valore• Se una struttura contiene un array l’array viene copiato!
– Attenzione quando si passano strutture con grossi campi array!
• Se voglio modificare una struttura devo sempre utilizzare i puntatori!
37
E le strutture ??? (2)• Es .
typedef struct studente {
char nom_cogn[40];
unsigned int matricola;
}studente;
void scambia (studente * s1, studente * s2);
38
E le strutture ??? (3)• Es .
void scambia (studente * s1, studente * s2){
…
(*s1).matricola = 4;
s1-> matricola = 4;
/*sono equivalenti */
}
39
E le strutture ??? (4)• Es . Come si dichiara una lista in C ?
– Usando i puntatoritypedef struct nodo {
struct nodo * next;
int info;
} nodo;
Mi serve il nome dellastruttura !
40
Allocazione dinamica della memoria
• La creazione di nuove variabili a run time (tipo new()), viene effettuata in C utilizzando le funzioni di libreria standard che permettono l’allocazione dinamica di porzioni contigue di memoria – malloc(), calloc(), realloc()– #include <stdlib.h> – es. Con queste primitive è possibile creare dinamicamente variabili e array di dimensione non nota a priori
41
Array dinamici -- malloc()• Vediamo come creare dinamicamente un array di 10 posizioni
int * a; /*conterrà il puntatore al
primo elemento dell’array*/
a = malloc(10*sizeof(int));
heap
40 byte
Punta all’indirizzo iniziale della nuovaarea allocata 100
96
42
Array dinamici -- malloc() (2)• Vediamo come creare dinamicamente un array di 10 posizioni
int * a; /*conterrà il puntatore al
primo elemento dell’array*/
a = malloc(10*sizeof(int));
if(a==NULL) printf(“fallimento!\n”);
heap
Se malloc non riesce ad allocarel’area di memoria richiesta restituisce NULL(verificare …..)
43
Array dinamici -- malloc() (3)• Vediamo come creare dinamicamente un array di 10 posizioni
int * a; /*conterrà il puntatore al
primo elemento dell’array*/
a = malloc(10*sizeof(int));
if(a==NULL) printf(“fallimento!\n”);
else {
a[4] = 345;
…
heapL’array si può accedere con i consuetioperatori (come se fosse statico)
40 byte10096
44
Array dinamici -- malloc() (4)• malloc() non inizializza la memoria a 0!
– Possiamo inizializzarla esplicitamente o– usare calloc()
int * a; /*conterrà il puntatore al
primo elemento dell’array*/
a = calloc(10,sizeof(int));
if(a==NULL) printf(“fallimento!\n”);
else {
a[4] = 345;
…
45
Array dinamici -- realloc() • realloc() modifica la lunghezza di un’area allocata precedentemente
int * a, *b; /*puntatori al
primo elemento
dell’array*/
a = malloc(10*sizeof(int));
….
b = realloc(a,20*sizeof(int));
/* adesso b punta ad un’array di 20 elementi */
46
Array dinamici -- realloc() (2)
• Meglio usare sempre due puntatori diversi (a,b) !– Altrimenti in caso di fallimento NULL sovrascrive il vecchio puntatore
47
Array dinamici -- free() • Lo spazio allocato sullo heap non viene deallocato all’uscita delle funzioni
• La deallocazione deve essere richiesta esplicitamente usando free()int * a;
a = malloc(10*sizeof(int));
….
free(a);
/* se qua accedo di nuovo ad a
può succedere di tutto */
48
tipo puntatore generico: void*• non si può dereferenziare• è necessario un cast prima di manipolare la variabile puntata.
– Es :void * c;
int a;
c = &a;
*c = 5; /* scorretto*/
*(int *)c = 5; /* corretto*/
49
tipo puntatore generico: void* (2)• Serve a scrivere funzioni ‘polimorfe’ in modo un po’ brutale• Es :
– il tipo della malloc() èvoid * malloc (unsigned int size);
– quando scrivoint * a;
a = malloc(10*sizeof(int));
– viene effettuato un cast implicito a (int *)
50
tipo puntatore generico: void* (3)
• Tipi delle altre funzioni di allocazione e deallocazionevoid * calloc (unsigned int size);
void * realloc (void * ptr,
unsigned int size);
void free (void * ptr);
51
I puntatori a funzione• Consideriamo la funzione :
int somma (int x, int y){
return x+y;
}
– se proviamo ad eseguire printf(“%p”,somma);
otteniamo un valore esadecimale che rappresenta un indirizzo legale del nostro programma – ??????????????????????????
52
I puntatori a funzione (2)• Consideriamo la funzione :
int somma (int x, int y){
return x+y;
}
Codice compilato di somma
IND
somma è un puntatore costante con valore pari a IND
53
I puntatori a funzione (3)• Consideriamo la funzione :
int somma (int x, int y){
return x+y;}
/* variabile di tipo funzione (int,int)->int */
int (*fun) (int,int);
int a;
fun = somma;
a = fun(3,5);
54
I puntatori a funzione (4)• Consideriamo la funzione :
int somma (int x, int y){
return x+y;}
/* variabile di tipo funzione (int,int)->int */
int (*fun) (int,int);
int a;
fun = somma;
a = fun(3,5);
Ma a che serve????????????
55
I puntatori a funzione (5)• Serve a definire funzioni che prendono come argomenti altre funzioni (di ordine superiore) :
void map (int (*fun) (int),
int x[], int l){
for(i=0;i<l;i++)
x[i] = fun(x[i]);
}
– è un iteratore che applica la funzione fun a tutti gli elementi dell’array x
56
I puntatori a funzione (6)• Esempio di uso della map :
int piu_uno (int x){
return x+1;}
int quad (int x){
return x*x;}
...
int a[3] = {3,4,5};
map(piu_uno,a,3); /* somma uno a tutti gli elementi */
map(quad,a,2); /* eleva al quadrato i primi due elementi */
57
Argomenti della linea di comando• Gli argomenti della linea di comando sono accessibili all’interno della funzione main()
– il SO li mette sulla pila prima di attivare il processo– il formato in cui sono resi disponibili è fisso
int main (int argc, char* argv [] ){
……
}
Numero di argomentinella linea di comando
Array di puntatoriagli argomenti(ciascuno è unastringa, tipo char*)
58
Argomenti della linea di comando (2)
• Un esempio :%> a.out una stringa per a.out
5
argc
argv p e r \O
a . o u t \O
s t r i n g
u n a \O
a \O
a . o u t \O
argv[0]
59
Argomenti della linea di comando (3)• Es. schema di programma che stampa gli argomenti sulla linea di comando :
int main (int argc, char* argv [] ){
……
for(i=0;i<argc;i++)
printf(“arg %d: %s”,i,argv[i]);
… …
}
60
Array multidimensionali come array di puntatori
• Vogliamo permettere la definizione di funzioni su array multidimensionali che non dipendono dal valore delle dimensioni successive alla primaint sum_mat (int ** MAT,
int n, int m){
…
}
Nome della matriceNumero di righe, colonne
61
Array multidimensionali come array di puntatori (2)
• Vogliamo accedere agli elementi dell’array usando la solita notazione [-][-]int sum_mat (int ** MAT,
int n, int m){
…
MAT[i][j] = …;
}
*(MAT + i*m + j)Il compilatore dovrebbe conoscere il legame fra questidue oggetti
62
Array multidimensionali come array di puntatori (3)
• Una alternativa: abbandonare l’allocazione contigua per righe :
MAT
7 2 6 2
1 2 3 4
4 6 1 2
4 5 1 2
MAT[0]
MAT[1]3 5 7 9
MAT[2]
MAT[3]
MAT[4]
MAT[2][2]
63
Array multidimensionali come array di puntatori (4)
• Una alternativa: vediamo i tipi ...
MAT
7 2 6 2
1 2 3 4
4 6 1 2
4 5 1 2
MAT[0]
MAT[1]3 5 7 9
MAT[2]
MAT[3]
MAT[4]
MAT[2][2]int**
int*[5]
int[4]
64
Array multidimensionali come array di puntatori (5)• Una alternativa: in memoria
non ho più aree contigue ...
MAT
1 2 3 4
4 6 1 2
MAT[0]
3 5 7 9
40 44 48 52
40 120140400 4
70 86827874
120 124 128 132 140 144 148 152
MAT[1] MAT[2]
indirizzo
valore
65
Array multidimensionali come array di puntatori (6)
• Perché funziona?
int sum_mat (int ** MAT,
int n, int m){
…
MAT[i][j] = …;
}
*(*(MAT + i) + j)Questo legame non interessa più!
66
Array multidimensionali come array di puntatori (7)
• Funzione di allocazioneint** mat_new(unsigned int m,
unsigned int n){int i, ** a, errore = FALSE;
a = malloc(m*sizeof(int*));
if (a==NULL) return NULL;
for(i=0;(i<m)&(!errore_riga); i++){
a[i] = malloc(n*sizeof(int));
if (a[i] ==NULL) errore = TRUE;
}
if (errore) /* ...gestione errori */
else return a;
}
67
Array multidimensionali come array di puntatori (8)
• Funzione di deallocazione
void mat_free(int ** a, unsigned int m)
{
int i;
/* dealloco tutte le righe */
for(i=0;i<m; i++)
free(a[i]);
/* dealloco il vettore dei puntatori alle righe */
free(a);
}
68
assert()
• Permettono di fare controlli di consistenza a tempo di esecuzione– prevenire meglio che curare …– es. un indice i e’ davvero dentro i limiti
dell’array?int x, a[N], i;
……
assert(i<N);
x = a[i];
69
assert() (2)
#include <assert.h>
…
assert(expr);– se expr è falsa il sistema stampa un messaggio
di errore e termina– se expr è vera non ha alcun effetto!
70
assert() (3)• Le assert costano!• Quando usarle
– per effettuare controlli di consistenza dopo chiamate a codice complesso
• tipicamente non abbiamo i sorgenti (se è scritto da altri)• … e comunque anche se li abbiamo non è mai garantita la
correttezza!
– si controlla se i valori risultanti sono rispondenti alle specifiche fornite
– si controllano invarianti noti dell’applicazione che stiamo scrivendo