Sei sulla pagina 1di 15

I Puntatori in C

ASD I

I puntatori
Una variabile è un'area di memoria alla quale è associato un
nome simbolico, scelto dal programmatore.
Detta area di memoria è grande quanto basta per contenere il tipo
di dato indicato nella dichiarazione della variabile stessa, ed è
collocata dal compilatore, automaticamente, in una parte di RAM
non ancora occupata da altri dati.
La posizione di una variabile in RAM è detta indirizzo, o
address.
Ad ogni variabile il compilatore associa quindi sempre due
valori:
il dato in essa contenuto e il suo indirizzo, cioè la sua posizione
in memoria.
ASD I

1
I puntatori
Un puntatore è il costrutto linguistico introdotto dal C (e da molti
altri linguaggi) come forma di accesso alla macchina sottostante.
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.

ASD I

I puntatori
Il C permette di manipolare in modo abbastanza semplice e diretto
gli indirizzi di memoria usando variabili di tipo puntatore .

int a;
int *pa;
a= 12 a=12;
pa= &a;
& printf(“Il contenuto dell’ indirizzo %p è %d \n”,pa,*pa);
*

pa = 0xAABBCCDD0000000
L’indirizzo di memoria (&) della
variabile a è 0xAABBCCDD0000000.
In questa locazione si trova una
variabile il cui contenuto (*) è 12.ASD I

2
I puntatori
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; /* OK */
Puntatori a tipi diversi sono incompatibili tra loro.
Esempio: il tipo del puntatore serve per
int x=8,*p; dedurre il tipo dell’oggetto puntato,
float *q; che è una informazione indispensabile
p = &x; /* OK */ per effettuare il dereferenziamento
q = p; /* NO! */
ASD I

I puntatori: l’operatore &


Un puntatore e' un tipo di dato. Una variabile puntatore è una
variabile che contiene l'indirizzo in memoria di un'altra variabile,
cioè un numero che indica in quale cella di memoria comincia la
variabile puntata. Si possono avere puntatori a qualsiasi tipo di
variabile.
La dichiarazione di un puntatore include il tipo dell'oggetto a cui
il puntatore punta; questo serve al compilatore che deve accedere
alla locazione di memoria per sapere le dimensioni della variabile
puntata dal puntatore.
L' operatore & fornisce l'indirizzo di una variabile.
Esempio:
tipo c, *p;
p=&c
scrive nella variabile p l'indirizzo della variabile c.
ASD I

3
I puntatori: l’operatore *
Quando una variabile di tipo puntatore è preceduta dall'operatore
*, esso indica che stiamo accedendo all'oggetto puntato dal
puntatore.
Quindi con *p indichiamo la variabile puntata dal puntatore.

Esempio:
int c, *p; c p

p = &c ; c p

c = 5; c=5 p

printf("%d\n", *p); //stampo 5 ASD I

Esempio: i puntatori

#include <stdio.h>
main( )
{ int x;
int * p, * q ; // p e q sono variabili puntatore a intero
p = &x; /* x non ha ancora un valore ma ha già un indirizzo. A questo
punto la cella x si può raggiungere sia usando il nome x, che attraverso il
puntatore p, scrivendo: *p */
q = p; /* ora anche q punta alla cella x */
*q = 3;
printf (" %d \n", x); /* output 3 */
x = x+1;
printf (" %d \n", *p); /* output 4 */
}
ASD I

4
Possibili valori dei puntatori
pointer: contenuto o valore della variabile pointer (indirizzo della
locazione di memoria a cui punta)
&pointer: indirizzo fisico della locazione di memoria del puntatore
*pointer: contenuto della locazione di memoria a cui punta

NB. Fino a quando il puntatore non viene inizializzato ad un


valore noto, esso punta ad una locazione di memoria casuale.
Così:
int *ip;
*ip=100;
scrive il valore 100 in una locazione qualsiasi.

ASD I

La locazione che vogliamo utilizzare è corretta?


Prima di scrivere un valore nella locazione di memoria puntata dal
puntatore ci assicuriamo che tale locazione appartenga al nostro
programma. Ciò e possibile in due modi:
1) assegniamo al puntatore l'indirizzo di una variabile del
programma, poi scriviamo il valore nella variabile puntata:
int *ip;
int x; x ip

ip=&x; x ip

*ip=100; x=100 ip

ASD I

5
La locazione che vogliamo utilizzare è corretta?
2) chiediamo al sistema operativo di riservare una porzione di
memoria e salviamo il relativo indirizzo nel puntatore.
La funzione di libreria standard malloc() permette l'allocazione
dinamica della memoria. Es.:
p
int *p;

p= (int *) malloc(sizeof(int));
assegna a p l’indirizzo di un nuovo spazio per un intero
La funzione malloc torna un puntatore a void; per cambiare il
tipo di ritorno serve utilizzare il cast al tipo scelto.
Se il valore di ritorno è pari a NULL, significa che non esiste
abbastanza spazio in memoria per soddisfare la richiesta.
ASD I

Puntatori e vettori
Quanti byte di memoria occupa un array? Dipende dal numero degli
elementi e dal tipo di dato dichiarato.
Un array di 20 interi occupa 40 byte (2 per ogni int).
Un array di 20 long ne occupa 80.
Calcoli analoghi occorrono per accedere ad uno qualsiasi degli
elementi di un array: il terzo elemento di un array di long ha indice 2
e dista 8 byte (2*4) dall'inizio dell'area di RAM riservata all'array
stesso. Il quarto elemento di un array di int dista 3*2 = 6 byte
dall'inizio dell'array.
Generalizzando, possiamo affermare che un generico elemento di un
array di un qualsiasi tipo dista dall'indirizzo base dell'array stesso un
numero di byte pari al prodotto tra il proprio indice e la dimensione
del tipo di dato.
ASD I

6
Puntatori e vettori
Il compilatore C consente di accedere agli elementi di un array in funzione di
un unico parametro: il loro indice. Per questo sono lecite e significative
istruzioni come quelle già viste:
iArr[1] = 12;
E' il compilatore ad occuparsi di effettuare i calcoli sopra descritti per ricavare
il giusto offset (distanza) in termini di byte di ogni elemento, e lo fa in modo
trasparente al programmatore per qualsiasi tipo di dato.
Ciò vale anche per le stringhe (o array di caratteri). Il fatto che ogni char
occupi un byte semplifica i calcoli ma non modifica i termini del problema.
Questo modo di gestire i puntatori ha due pregi:
•da un lato evita al programmatore lo sforzo di pensare ai dati in memoria in
termini di numero di byte;
•dall'altro, consente la portabilità dei programmi che fanno uso di puntatori
anche su macchine che codificano gli stessi tipi di dato con un diverso
numero di bit. ASD I

Puntatori e vettori
Puntatori e vettori sono strettamente correlati: il nome del vettore è
un sinonimo del puntatore al suo primo elemento.
int a[10];
int *pa;
pa=&a[0]; //pa punta al primo elemento di a
pa=a; //pa punta ancora al primo elemento di a
*a==a[0]; //sempre VERO

a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]

a
ASD I

7
Conseguenza
Il fatto che il nome dell’array indichi non l’array in sé, ma
l’indirizzo iniziale dell’area di memoria ad esso associata ha, come
già visto, una importante conseguenza:
È impossibile denotare un array nella sua globalità, in qualunque
contesto. Quindi, se il problema fosse: Data una stringa di
caratteri, copiarla in un altro array di caratteri (di lunghezza
non inferiore), non potremmo copiare tutto in una volta una
stringa nell’altra. Questo assegnamento viene interpretato
come il tentativo di cambiare l’indirizzo
main() { iniziale della stringa s2, cosa impossibile.
char s[] = "Nel mezzo del cammin di";
char s2[40];
ERRORE DI COMPILAZIONE:
s2 = s;
incompatible types in assignment
ASD I

Puntatori e vettori int a[10];


Operazioni lecite sui puntatori: int *p,*q;
•somma e sottrazione di interi p=a+4; //1
•incremento e decremento q=--p; //2
•differenza tra puntatori omogenei
2 ++p
q= --p p+4

a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]

1
p

ASD I

8
Vettori di Puntatori
Vogliamo costruire dinamicamente un dato M che sia un array
di n puntatori ad intero, per poi allocare, per ciascuno dei
puntatori ad int dell'array, spazio sufficiente ad m interi,
ottenendo un array di vettori di interi.
M M[2][4]

M[0]

M[1]

M[2]

M[3]

M[n] ASD I

Vettori di Puntatori
Se gli elementi dell'array dinamico M sono puntatori ad interi, il
puntatore M sarà un puntatore di puntatore ad intero, cioè sarà un
int* *M;
L'elemento intero che sta nella posizione c-esima dell'r-esimo
vettore di interi verrà indicato mediante l'espressione: M[r][c]

In effetti questa struttura dati è spesso usata come matrice in


C, quando non sono note a priori le dimensioni della matrice
da realizzare, e si preferisce non sovradimensionarla con
un'allocazione statica.

ASD I

9
Vettori di Puntatori
/* allocazione della struttura dati */ Il vettore di
int n,m r,c; puntatori viene
int* *M; utilizzato nel caso
/* valori ad n ed m assegnati run-time */ particolare in cui
n = 20; tutti i vettori allocati
m = 15; abbiano la stessa
if ( ! (M = malloc( n * sizeof(int*) )) ) dimensione. In
exit(1); generale, è possibile
for ( r=0; r<n; r++) per ogni puntatore
if ( ! (M[r] = malloc( m * sizeof(int) )) ) allocare un vettore
exit(1); di dimensioni
else diverse.
for ( c=0; c<m; c++)
M[r][c] = valore....... ASD I

Esempio malloc
Per richiedere 12 byte di spazio in memoria:
int *p;
troppo specifico!
p = (int*) malloc(12);
Per farsi dare lo spazio necessario per 5 interi (qualunque esso
sia):
int *p;
p = (int*) malloc(5*sizeof(int));
L’operatore sizeof consente di essere indipendenti dalle scelte dello
specifico compilatore e della specifica macchina.

ASD I

10
Esempio malloc
L’area allocata è usabile, equivalentemente:
•o tramite la notazione a puntatore ( *p )
•o tramite la notazione ad array ( [ ] )
int *p;
p=(int*)malloc(5*sizeof(int)); p
p[0] = 13;
p[1] = 18; ...
*(p+4) = -20;

Attenzione a non “sforare”!


Non c’è alcun controllo!!

ASD I

Esempio malloc
Abbiamo costruito un array dinamico, le cui dimensioni:
•non sono determinate a priori
•possono essere scelte dal programma in base alle esigenze del
momento.
L’espressione passata a malloc() può infatti contenere variabili.

ASD I

11
Deallocazione
Quando non serve più, l’area allocata deve essere esplicitamente
deallocata
•ciò segnala al sistema operativo che quell’area è da
considerare nuovamente disponibile per altri usi.

La deallocazione si effettua mediante la funzione di libreria free()


int *p=(int*)malloc(5*sizeof(int));
...
free(p);

Non è necessario specificare la dimensione del blocco da


deallocare, perché il sistema la conosce già.

ASD I

Aree dinamiche: tempo di vita


Il tempo di vita di una variabile dinamica non è legato a quello
delle funzioni
•in particolare, non è legato al tempo di vita della funzione che
l’ha creata quindi, una variabile dinamica può sopravvivere alla
funzione che l’ha creata
Ciò consente di:

•creare un’area dinamica in una funzione...

•... usarla in un’altra funzione...

•... e distruggerla in una terza funzione!

ASD I

12
Deallocazione
Il modello di gestione della memoria dinamica del C richiede che
l'utente si faccia esplicitamente carico anche della deallocazione
della memoria.
È un approccio pericoloso: molti errori sono causati proprio da
un’errata deallocazione !
•rischio di puntatori che puntano ad aree di memoria non più
esistenti (dangling reference)
Altri linguaggi gestiscono automaticamente la deallocazione,
(garbage collector: è dimostrato che l’uso di un garbage collector
riduce drasticamente il numero di errori legati all’uso dei
puntatori, però, il garbage collector è un’entità fuori dal nostro
controllo, che parte “quando vuole lui” quindi inadatto ai sistemi
in tempo reale)
ASD I

Puntatori a strutture
La definizione di questo tipo di puntatore prevede di definire il
tipo di dato, e poi di definire il puntatore a quel tipo di dato.
Es.
struct PUNTO {
char nome[50];
int x;
int y;
int z;
};
struct PUNTO *ptrpunto;
struct PUNTO punto;
ptrpunto = &punto;
ASD I

13
Puntatori a strutture
E' necessario definire un operatore che permetta di accedere ai
campi della struttura direttamente dal puntatore.
In C esiste l'operatore - > che permette l'accesso ad un campo
della struttura puntata.
Esempio:
ptrpunto- > x = 102;
accede in scrittura al campo x della struttura di tipo PUNTO
puntata da ptrpunto.
Equivale all'istruzione:
(*ptrpunto).x = 102;

ASD I

Puntatori all’interno di strutture


Vogliamo definire una struttura dati che serva come nodo di una
lista semplice, una struttura che contenga cioè, ad esempio, un
intero (il dato della lista) ed un puntatore all'elemento successivo
della lista.
Il problema è come definire un puntatore ad un tipo di dato
mentre ancora il tipo di dato non è stato definito.

ASD I

14
Puntatori all’interno di strutture
struct nodolista {
int id;
struct nodolista *next;
};
struct nodolista nodo;

typedef struct nodolista {


int id;
struct nodolista *next;
} NODOLISTA;
NODOLISTA nodo;

ASD I

15

Potrebbero piacerti anche