Sei sulla pagina 1di 18

Puntatori,

aritmetica dei puntatori,


memoria dinamica.

Funzioni di I/O – p.1/36

Richiami
Se T è un htypenamei, la dichiarazione

T *p;

dichiara p come un puntatore a T .


p può contenere l’indirizzo di qualunque variabile di tipo
T.
Il tipo di p è T *.
L’operatore &hvariabilei ritorna l’indirizzo della variabile
argomento:

p=&var;

si dice ora che p punta a var.

Funzioni di I/O – p.2/36


Richiami
Dereferenziazione (operatore *). L’espressione *p ha
tipo tipo T e può sostituire var in tutto, assegnazione
compresa (accede alla stessa zona di memoria).
Uso “elementare”: implementare il passaggio di
parametri “by reference”.

void raddoppia(int *x)


{
*x=2*(*x);
}
...
int a=8;
raddoppia(&a); // a=16

Funzioni di I/O – p.3/36

Aritmetica
Aritmetica dei puntatori. Sia
x un array monodimensionale (vettore) di N elementi di
tipo T ;
P , Q espressioni di tipo T *,
k espressione di tipo intero, 0 ≤ k ≤ N − 1.

È noto che
Se P =&x[0], e
l è un’espressione di tipo intero, con 0 ≤ l ≤ N − 1:
P [l] equivale a x[l].

Funzioni di I/O – p.4/36


Aritmetica
int x[5]={1,2,3,4,5};

void raddoppia_vett(int *v, int n)


{
int i;
for(i=0; i<n; i++)
v[i]=2*v[i];
}
...
raddoppia_vett(x,5); // x=(2,4,6,8,10)

Funzioni di I/O – p.5/36

Aritmetica
Vale un meccanismo molto più generale.
Un vettore residente in memoria rappresenta una sorta
di spazio di indirizzi, all’interno del quale è definita
un’aritmetica degli indirizzi (confronti, modifica con
operatori opportuni . . . ).
Se P =&x[k ], e
l è un’espressione di tipo intero, con 0 ≤ k + l ≤ N − 1:
P [l] equivale a x[k + l].

Funzioni di I/O – p.6/36


Aritmetica
P + l è un’espressione di tipo T * e punta a x[k + l].
*(P + l) equivale a x[k + l].
Se P punta a x[k ] e Q punta a x[l], risulta vero

P <Q (risp. P ==Q, P >Q)

se e solo se k < l (risp. k > l, k = l).


L’espressione (Q − P ) ha tipo intero (opportunamente
grande) e vale (l − k).
L’aritmetica dei puntatori (somme e sottrazioni con
interi, differenze, confronti) è consistente fintanto che i
puntatori generati rimangono all’interno dello stesso
vettore.
Ogni operazione che genera puntatori esterni al vettore
ha risultato indefinito. Funzioni di I/O – p.7/36

Aritmetica
Se P =&x[0] e Q =&x[k ], risulta P +N >Q.
— Da non dereferenziare mai!
Nella valutazione di un’espressione, se x è il nome di
un array, x viene automaticamente convertito nel
puntatore &x[0]. Non accade il contrario!
Nomi di array e puntatori sono quasi intercambiabili
nelle espressioni.
char nome[]="Pippo", *str;
str=nome; /* Corretto: converte nome[] in un ch
/* come str=&nome[0]; */
nome=str; /* Errore! */
Nei parametri di funzione, T [] è sinonimo di T *.
Ecco perché gli array sono passati per riferimento.
Funzioni di I/O – p.8/36
Aritmetica
int x[]={2,4,6,8,10};
int *p, *q;

p=x; // p=&x[0]
q=x+4; // q=&x[4]
printf( "q-p=%d\n", (q-p) ); // Stampa 4
p++; // ora p=&x[1], come p=p+1;
printf( "%d, %d\n", *p, p[1] ); // Stampa 4, 6
if( p<q ) {...} // Vero!
if( p<(q+7) ) {...} // Indeterminato!
*(p+1)=3; // x[2]=3
*(q+7)=3; // Indeterminato!

Funzioni di I/O – p.9/36

Aritmetica
Esempio. Calcolo della norma euclidea di un vettore di n
elementi.
double norm(double *x, int n)
{
int i;
double sum=0.0;
for(i=0; i<n; i++)
sum+=x[i]*x[i];
return sqrt(sum); /* Radice quadrata */
}
...
double z[MAXDIM], modulo;
...
modulo=norm(z,10);

Funzioni di I/O – p.10/36


Aritmetica
Risulta
1
modulo = (z[0]2 + z[1]2 + · · · + z[9]2 ) 2 .

Se si vuole calcolare la norma del sottovettore

(z[3], . . . , z[9])

basta richiamare
modulo=norm(&z[3],7); oppure
modulo=norm(z+3,7);

Funzioni di I/O – p.11/36

Aritmetica
L’aritmetica dei puntatori è diventata rapidamente
popolare in C, perché
consente accessi più veloci agli array (almeno, una
volta);
permette un accesso low-level ma comunque con
una certa astrazione alla memoria.
Attenzione: in questo caso si fa spesso uso di caratteristiche dei puntatori legate
all’implementazione!

L’aritmetica dei puntatori è una delle caratteristiche del


C che consentono di scrivere codice molto compatto
(ma anche un po’ enigmatico).
int i;main(){for(;i["]<i;++i){--i;}"];read(’-’-’-’,i+++"hell\
o, world!\n",’/’/’/’));}read(j,i,p){write(j/p+p,i---j,i/i);}

Funzioni di I/O – p.12/36


Aritmetica
Esempio. Un programmatore C esperto potrebbe scrivere
double norm(double *x, int n)
{
double sum=0.0;
for(;x<(x+n); x++) sum+= (*x)*(*x);
return sqrt(sum);
}
oppure ancora
double norm(double *x, int n)
{
double sum=0.0;
for(;n>0; x++, n--) sum+= (*x)*(*x);
return sqrt(sum);
}

Funzioni di I/O – p.13/36

Aritmetica
Esempio. Scrivere una funzione

void selsort(int *v, int n)

che ordini gli interi di un vettore v[] di lunghezza n.


int x={5,6,1,9,8,2};
selsort(x+2,4); // x=(5,6,1,8,9,2)
selsort(x,6); // x=(1,2,5,6,8,9)

Funzioni di I/O – p.14/36


Aritmetica
Il selection sort ordina un vettore (v0 , . . . , vn−1 ) come segue.
Per ogni i = 0, . . . , n − 1:
Cerca nel vettore (vi , . . . , vn−1 ) l’elemento più
piccolo, sia esso vp ;
Scambia vi ⇐⇒ vp ;

Funzioni di I/O – p.15/36

Aritmetica
Implementare una funzione

int min_pos(int *v, int k, int m)

che ritorni la posizione del minimo elemento nel


sottovettore (v[k]...v[m]).
Implementare una funzione

void scambia(int *v, int i, int j)

che esegue v[i] ⇐⇒ v[j].


Implementare selsort() usando queste funzioni.

Funzioni di I/O – p.16/36


Aritmetica
int min_pos(int *v, int k, int m)
{ int i, res=k;
for(i=k+1; i<=m; i++)
if(v[i]<v[res]) res=i;
return res;
}

void scambia(int *v, int i, int j)


{ int tmp=v[i];
v[i]=v[j];
v[j]=tmp;
}

Funzioni di I/O – p.17/36

Aritmetica
void selsort(int *v, int n)
{
int i;
for(i=0; i<n-1; i++) {
scambia(v,i,min_pos(v,i,n-1));
}
}

Funzioni di I/O – p.18/36


Aritmetica
Versione con uso “massiccio” di puntatori.
Implementare una funzione

void selsort(int *begin, int *end)

che ordina il (sotto)vettore di interi compresi tra le celle


puntate da begin e end.
Nota. La funzione assume che begin, end puntino ad
elementi di uno stesso vettore e che begin<=end
(condizioni da garantire a cura del chiamante).

Funzioni di I/O – p.19/36

Aritmetica
Implementare una funzione

int *min_pos(int *begin, int *end)

che ritorna un puntatore all’elemento più piccolo


contenuto tra le posizioni begin e end.
Implementare una funzione

void scambia(int *i, int *j)

che scambia gli interi puntati da i e j.


Implementare

void selsort(int *begin, int *end)

richiamando opportunamente min_pos() e


scambia(). Funzioni di I/O – p.20/36
Aritmetica
void scambia(int *i, int *j)
{ int tmp=*i;
*i=*j;
*j=tmp;
}

int *min_pos(int *begin, int *end)


{
int *res=begin;
for(begin+=1; begin<=end; begin++)
if( *begin<*res )
res=begin;
return res;
}

Funzioni di I/O – p.21/36

Aritmetica
void selsort(int *begin, int *end)
{
for(;begin<end;begin++)
scambia(begin, min_pos(begin,end));
}

Funzioni di I/O – p.22/36


Assegnazioni e conversioni
Puntatori generici.
Un puntatore di tipo T * contiene solo indirizzi validi per
variabili di tipo T .
Un puntatore di tipo

void *

può contenere indirizzi di variabili di qualunque tipo.


void nelle dichiarazioni di puntatori agisce come un
htypenamei, ma non esistono variabili di tipo void.
Le variabili di tipo void * non possono essere
dereferenziate.

Funzioni di I/O – p.23/36

Assegnazioni e conversioni
void x; /* Errore! */
void *p; /* Ok, puntatore generico */
int i;
double y;
p=&i; /* Ok */
p=&y; /* Ok */
*p=0.0; /* Errore! */

Funzioni di I/O – p.24/36


Assegnazioni e conversioni
Assegnazione/copia di puntatori.
L’assegnazione è sempre possibile tra puntatori allo
stesso tipo.
L’assegnamento (conversione) tra due puntatori a tipi
diversi è accettato solo se uno dei due ha tipo void *;
altrimenti la conversione va forzata, ma
gli effetti della conversione forzata dipendono
dall’implementazione (=ogni compilatore può trattare la
conversione forzata in modo specifico).

Funzioni di I/O – p.25/36

Assegnazioni e conversioni
int x=0, y, *px, *py;
void *p;
double d, *pa;
px=&x;
py=&y;
pa=px; /* Errore! */
py=px; /* Ok, stesso tipo puntato */
*py+=10; /* x=10 */
p=px; /* Ok, void* a sin.*/
pa=p; /* Ok, void* a destra*/
*pa=3.0; // x=??? dip. implem.
pa=&d;
py=(int *)pa; /* Ok, conversione forzata */
*py=1; /* d=??? dip. implem.*/

Funzioni di I/O – p.26/36


Assegnazioni e conversioni
Puntatore nullo.
Un intero si può assegnare (con conversione forzata)
ad un puntatore.
Il risultato dipende dall’implementazione.

int *ptr=(int*) 256; //???

L’intero 0 si può assegnare a qualunque puntatore


senza conversione forzata.
Lo standard garantisce che nessun oggetto valido abbia
indirizzo 0 (puntatore “nullo”).
Più spesso in luogo dello 0 si usa la costante simbolica
NULL.

Funzioni di I/O – p.27/36

Assegnazioni e conversioni
Ad un puntatore con valore NULL non si deve mai
applicare l’operatore * (dereferenziazione).
double *p=NULL;
*p=0.5; // Errore!
NULL è definito in vari header file (stddef.h,stdio.h,
stdlib.h, . . . ).

Funzioni di I/O – p.28/36


Memoria dinamica
La libreria standard del C fornisce funzioni utili per l’uso
della memoria heap.
Includere:

#include <stdlib.h>

Blocchi di memoria possono essere riservati nello heap


(e successivamente rilasciati) a richiesta.
La persistenza di questi blocchi non è limitata al blocco
nel quale essi vengono richiesti

Funzioni di I/O – p.29/36

Memoria dinamica
void *malloc(size_t size);
Riserva (alloca) sullo heap un blocco di size byte.
Il tipo size_t è un tipo intero “grande abbastanza”.
Ritorna un puntatore corrispondente all’inizio del blocco
riservato.
Ritorna NULL se l’allocazione non può essere effettuata
(es. out of memory).
Il contenuto iniziale del blocco è indefinito (la memoria è
“sporca”).

Funzioni di I/O – p.30/36


Memoria dinamica
L’operatore sizeof.
Si applica a nomi di tipo, oppure a variabili.
Se T è un nome di tipo, sizeof(T ) è il numero di byte
richiesti per memorizzare una variabile di tipo T .
Se X è una variabile, sizeof(X ) è il numero di byte
occupati in memoria da X .
Se v è un vettore di N elementi di tipo T , lo standard
garantisce che v occupi in memoria esattamente

N sizeof(T )* byte.

Funzioni di I/O – p.31/36

Memoria dinamica
Allocazione dinamica di array.
Se N è un’espressione di tipo intero,
e T un nome di tipo, chiamando

malloc(N *sizeof(T ),

la malloc() alloca la memoria corrispondente ad un


vettore di N elementi di tipo T , e ritorna il puntatore al
primo elemento.

Funzioni di I/O – p.32/36


Memoria dinamica
Esempio.
float *v;
int i, n;

printf( "Quanti elementi vuoi allocare? " );


scanf("%d", &n);
v=(float *)malloc(n*sizeof(float));
/* Non sono obbligato a sovradimensionare ... */
/* ... e uso v come un normale vettore! */
if( v!=NULL ) /* Tutto ok, c’e’ memoria */
for(i=0; i<n; i++) {
printf( "Introduci v[%d]: ", i );
scanf("%f", &v[i]);
}
else {...} /* Segnala out-of-memory. */
Funzioni di I/O – p.33/36

Memoria dinamica
void free(void *p);
Rilascia il blocco di memoria al quale punta p.
Il valore di p deve essere stato ottenuto come risultato
di una malloc(), altrimenti l’effetto della free() è
indefinito.
Le variabili allocate nello heap per mezzo di malloc()
non cessano di esistere all’uscita dal blocco nel quale
sono state allocate.
Perché la memoria corrispondente sia liberata occorre
una free() esplicita (no garbage collection.).
Dopo la free(), l’indirizzo contenuto in p non è più
“affidabile” (e non deve essere dereferenziato).

Funzioni di I/O – p.34/36


Memoria dinamica
Norma di buona programmazione. Ogni regione di memo-
ria ottenuta per mezzo di malloc() deve essere liberata
da una corrispondente free() quando essa non viene più
utilizzata. Altrimenti si rischia di creare programmi che “in-
tasano” la memoria.

Funzioni di I/O – p.35/36

Memoria dinamica
Esempio.
float *v;
int i, n;

printf( "Quanti elementi vuoi allocare? " );


scanf("%d", &n);
v=(float *)malloc(n*sizeof(float));
... /* elabora... */
free(v);

Funzioni di I/O – p.36/36

Potrebbero piacerti anche