Il linguaggio C -...

Post on 25-Aug-2020

0 views 0 download

transcript

1

Il linguaggio C

Puntatori e dintorni

2

Puntatori : idea di base

• In C è possibile conoscere e denotarel’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 dellacella in cui è memorizzata a */

3

Puntatori : idea di base (2)

• In C è possibile conoscere e denotarel’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 tiponometype

• 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 manipolandodirettamente 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 denotail puntatore nullo

• È possibile definire puntatori per tutti i tipibase 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 puntatoreusando alcuni degli usuali operatoriaritmetici (+, -, --, ++)– int a[3], *p = &a[0]; ……IN+12

IN+8IN+4IN

a[2]a[1]a[0]

p contiene l’indirizzo IN

9

Aritmetica dei puntatori (2)

• È possibile scrivere espressioni puntatoreusando alcuni degli usuali operatoriaritmeticiint 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 puntatoreusando alcuni degli usuali operatoriaritmeticiint 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 puntatoreusando alcuni degli usuali operatoriaritmetici (+, -, --, ++)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 puntatoreusando alcuni degli usuali operatoriaritmetici (+, -, --, ++)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’arrayint a[3], *p = &a[0], *q;q = a; ……IN+12

IN+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 sonoequivalenti */

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 qualsiasivariabile puntatoreint 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

equivalentiint 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 caratterichar a[7] = “ciao”;– le stringhe costanti possono essere definite

anche comeconst char * pp = “ciao”;char * pp = “ciao”;

• attenzione! Se a questo punto cercate di modificareun elemento di pp (es. pp[2] = `f`;) avete unerrore a run time

• mentre a[2] = `f`; è completamente corretto

19

Passaggio di parametri perriferimento

• Tutti i parametri delle funzioni C sonopassati 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 variabilenell’ambiente del chiamante

20

Passaggio di parametri perriferimento (2)

• Esempio : la funzione che scambia fra loro ivalori 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 perriferimento (3)

• Esempio : la funzione che scambia fra loro ivalori 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 fattosulle copie

22

Passaggio di parametri perriferimento (3.1)

• Esempio : la funzione che scambia fra loro ivalori 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 perriferimento (3.2)

• Esempio : la funzione che scambia fra loro ivalori di due variabili– alla fine dell’esecuzione di scambia (prima di

ritornare al chiamante)int a = 4, b = 5;scambia(a,b); 4

5

54

Framechiamante

&a&b

Framescambia

&x&y

24

Passaggio di parametri perriferimento (3.3)

• Esempio : la funzione che scambia fra loro ivalori di due variabili– al momento di eseguire la printf (il frame di

scambia non è più significativo)int a = 4, b = 5;scambia(a,b);printf(“%d,%d”,a,b);

45

Framechiamante

&a&b

25

Passaggio di parametri perriferimento (4)

• Esempio : la funzione che scambia fra loro ivalori 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 perriferimento (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 perriferimento (4.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 perriferimento (4.3)

• Esempio : versione corretta di scambia ...– al momento di eseguire la printf (il frame di

scambia non è più significativo)int a = 4, b = 5;scambia(&a,&b);printf(“%d,%d”,a,b);

54

Framechiamante

&a&b

29

Passaggio di parametri perriferimento (5)

• ATTENZIONE : gli array sono passatisempre per riferimento perché quello che sipassa è il nome dell’array

void assegna (int x[ ]) { x[0] = 13;}

– con chiamataint a[10];assegna(a);/* qua a[0] vale 13 */

30

Passaggio di parametri perriferimento (6)

• Inoltre : le due scritturevoid assegna (int x[ ]){ x[0] = 13;}

evoid assegna (int *x){ x[0] = 13;}

– sono del tutto equivalenti– si preferisce usare la prima per leggibilità

31

Passaggio di parametri perriferimento (7)

• Tipicamente le funzioni che lavorano suarray hanno un secondo parametro chefornisce 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 dilunghezza l

32

Passaggio di parametri perriferimento (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 perriferimento (9)

• Perché dobbiamo specificare l’ampiezza diuna 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 perriferimento (10)

• Se non conosco la lunghezza della secondadimensione il compilatore non riesce agenerare 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 perriferimento (11)

• Esiste un modo migliore di quello appenaesaminato per la rappresentazione degli arraymultidimensionali in C : lo vedremo in seguito

36

E le strutture ???

• Le strutture vengono sempre passate pervalore

• Se una struttura contiene un array, alloral’array viene copiato!– Attenzione quando si passano strutture con

campi array molto grandi!• Se voglio modificare una struttura devo

sempre utilizzare i puntatori!

37

E le strutture ??? (2)

• Esempio

typedef struct studente { char nom_cogn[40]; unsigned int matricola;} studente;

void scambia (studente * s1,studente * s2);

38

E le strutture ??? (3)

• Esempio

void scambia (studente * s1,studente * s2) {

…(*s1).matricola = 4; s1->matricola = 4;/*sono equivalenti */}

39

E le strutture ??? (4)

• Esempio : Come si dichiara una lista in C ?– Usando i puntatori

typedef struct nodo { struct nodo * next; int info;} nodo;

Mi serve il nome dellastruttura !

40

Allocazione dinamica dellamemoria

• La creazione di nuove variabili a run time(tipo new in Java) viene effettuata in Cutilizzando le funzioni di libreria standardche permettono l’allocazione dinamica diporzioni contigue di memoria– malloc, calloc, realloc– #include <stdlib.h>

Con queste primitive è possibile crearedinamicamente variabili e array di dimensionenon nota a priori

41

Array dinamici -- malloc( )

• Vediamo come creare dinamicamente un arraydi 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 arraydi 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 arraydi 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’areaallocata 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 20elementi */

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 vienedeallocato all’uscita delle funzioni

• La deallocazione deve essere richiestaesplicitamente usando free

int * a;a = malloc(10*sizeof(int));…free(a);/* se qua accedo di nuovo ad apuò 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 unamaniera un po’ brutale

• Esempio– 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 edeallocazione

void * calloc (unsigned int size);void * realloc (void * ptr, unsigned int size);void free (void * ptr);

51

I puntatori a funzione

• Consideriamo la funzioneint somma (int x, int y) { return x + y;}

– se proviamo ad eseguireprintf(“%p”, somma);

otteniamo un valore esadecimale cherappresenta un indirizzo legale del nostroprogramma

– ??????????????????????????

52

I puntatori a funzione (2)

• Consideriamo la funzioneint 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 funzioneint 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 funzioneint 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 ordinesuperiore)

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 atutti 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 atutti gli elementi */

map(quad,a,2); /* eleva al quadratoi 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 inserisce sullo stack 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)• 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)• Esempio : Schema di programma che stampa

gli argomenti sulla linea di comandoint main (int argc, char * argv[ ]) { … for(i = 0; i < argc; i++) printf(“arg %d: %s”, i, argv[i]);…

}

60

Array multidimensionali comearray di puntatori

• Vogliamo permettere la definizione difunzioni su array multidimensionali che nondipendono dal valore delle dimensionisuccessive alla prima

int sum_mat (int ** MAT, int n, int m) {

}

Nome della matriceNumero di righe, colonne

61

Array multidimensionali comearray di puntatori (2)

• Vogliamo accedere agli elementi dell’arrayusando 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 comearray di puntatori (3)

• Una alternativa: abbandonare l’allocazionecontigua 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 comearray 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 comearray di puntatori (5)

• Una alternativa: in memorianon 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 comearray 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 comearray 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); i++) {

a[i] = malloc(n*sizeof(int)); if (a[i] == NULL) errore = TRUE; } if (errore) /* gestione errori */ else return a;}

67

Array multidimensionali comearray 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 l’array dei puntatori alle

righe */ free(a);}

68

assert( )• Permettono di fare controlli di consistenza a

tempo di esecuzione– prevenire meglio che curare …– esempio: un indice i è 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 dopochiamate a codice complesso

• tipicamente non abbiamo i sorgenti (se è scritto daaltri)

• … e comunque anche se li abbiamo non è maigarantita la correttezza!

– si controlla se i valori risultanti sonorispondenti alle specifiche fornite

– si controllano invarianti noti dell’applicazioneche stiamo scrivendo