Sei sulla pagina 1di 51

Corso di Fondamenti di Informatica

Ingegneria delle Comunicazioni – BCOR


Ingegneria Elettronica – BELR

Introduzione al C

Unità 6 – Puntatori

D. Bloisi, A. Pennisi, S. Peluso, S. Salza


Sommario – Unità 6
•  Memoria, indirizzamento e puntatori
•  Tipo void* e conversione sui parametri
•  Gestione dinamica della memoria
•  Tempo di vita delle variabili allocate
dinamicamente
•  Problemi di deallocazione della memoria
•  Passaggio dei parametri tramite puntatori

Puntatori - Unità 6 2013/2014 Pagina 2


Indirizzamento e puntatori

In C il programmatore ha la possibilità di gestire


gli indirizzi attraverso delle variabili definite di
tipo puntatore.

I valori delle variabili di tipo puntatore sono


indirizzi di memoria, ossia dei valori numerici
che fanno riferimento a specifiche locazioni di
memoria.

Puntatori - Unità 6 2013/2014 Pagina 3


Notazione grafica
L’indirizzamento si può rappresentare
graficamente mediante una freccia.

Spesso non occorre conoscere lo specifico valore di


una variabile di tipo puntatore (es. 00100).

Puntatori - Unità 6 2013/2014 Pagina 4


Operatore & (indirizzo-di) (1/2)
Per ottenere dei valori di tipo puntatore, cioè
degli indirizzi, si utilizza l’operatore &
int i = 1;
printf("L\' indirizzo di i e\' "
"%p\n", &i);
printf("mentre il valore di i e\' "
"%d\n", i);
stampa
L' indirizzo di i e' 0028FF1C
mentre il valore di i e' 1

Puntatori - Unità 6 2013/2014 Pagina 5


Operatore & (indirizzo-di) (2/2)
L’operatore & si chiama operatore indirizzo-di e
restituisce l’indirizzo della variabile a cui viene
applicato.
Nota:
i = &10;
non è ammesso dato che 10 è una costante int.
Analogamente non si può accedere all’indirizzo di
una espressione.

Nota: l’indicatore di conversione per un indirizzo è


%p (il formato è definito dall’implementazione)

Puntatori - Unità 6 2013/2014 Pagina 6


Operatore * di dereferenziamento
(indirizzamento indiretto) (1/2)
Quando si considera una variabile di tipo puntatore,
l’operatore * permette di recuperare il valore della
locazione di memoria puntata.
int i, j = 1;
i = *&j;
L’istruzione i = *&j; assegna (con una notazione un po’
complicata) il valore della variabile j alla variabile i.
Equivale, in pratica, all’istruzione i = j;
L’operatore (unario) di indirizzamento indiretto * non
deve essere confuso con l’operatore di moltiplicazione
(binario).
Puntatori - Unità 6 2013/2014 Pagina 7
Operatore * di dereferenziamento
(indirizzamento indiretto) (2/2)
int i, j = 1;
00100 00200

i ? j 1

i = *&j; &j ≡ 00200

*(&j) ≡ Valore contenuto in 00200 ≡ 1


00100 00200

i 1 j 1
Puntatori - Unità 6 2013/2014 Pagina 8
Operatore & vs. operator *!
Se x è una variabile, &x denota l’indirizzo in memoria di
tale variabile.
&x ≡ α
indirizzo
nome α

Se α è l’indirizzo in memoria di una variabile, *α denota


tale variabile:
*α ≡ x
Puntatori - Unità 6 2013/2014 Pagina 9
Variabili di tipo puntatore
Per la gestione degli indirizzi occorre dichiarare delle
variabili di tipo puntatore, specificando il tipo della
locazione di memoria puntata.

int *p1;
... // allocazione di memoria (vedi dopo)
*p1 = 10;

dichiara una variabile di tipo puntatore ad intero ed


assegna alla variabile puntata il valore 10.

Puntatori - Unità 6 2013/2014 Pagina 10


Attenzione
1. La dichiarazione di una variabile puntatore
non alloca memoria per la variabile puntata;
prima di accedere alla variabile puntata
bisogna allocare esplicitamente memoria
(vedremo dopo come …)

2. Nelle dichiarazioni multiple tipo:


int *p1, p2;
p2 non è un puntatore!

Puntatori - Unità 6 2013/2014 Pagina 11


Esempio: uso di variabili puntatore

int i, j, k;
int *pt_i, *pt_j;
pt_i = &i;
pt_j = &j;
i = 1;
j = 2;
k = *pt_i + *pt_j;
*pt_i = 10;
printf("i = %d\n", i);
printf("k = %d\n", k);
Puntatori - Unità 6 2013/2014 Pagina 12
Diagramma della memoria
Le variabili di tipo int i, j, k, vengono manipolate
attraverso i puntatori alle locazioni di memoria ad esse
associate al momento della dichiarazione.

Puntatori - Unità 6 2013/2014 Pagina 13


Esecuzione
Il programma stampa a video:

i = 10
k = 3

Puntatori - Unità 6 2013/2014 Pagina 14


Assegnazione di un valore specifico a
puntatore
#include <stdio.h>

int main() {
int *ptr;
ptr = 1000;
Cosa stampa
*ptr = 5;
printf("%d\n", *ptr); questo codice?
}

Output del compilatore

warning: assignment makes pointer from


integer without a cast
Puntatori - Unità 6 2013/2014 Pagina 15
Accessi in memoria
int i, j = 2, *pt;
pt = &j;
i = *pt;

per ottenere il valore da assegnare a i devono essere fatti


2 accessi in memoria:

• il primo accesso viene fatto all’indirizzo di pt, per


recuperare il dato ivi memorizzato, i.e. l’indirizzo di j

• il secondo accesso avviene all’indirizzo di j, per


recuperare il valore memorizzato nella variabile j

Puntatori - Unità 6 2013/2014 Pagina 16


Esempio sui puntatori
Consideriamo gli effetti del seguente codice:

int *pointer; // dichiara pointer come un


// puntatore a int
int x = 1, y = 2;
pointer = &x; // (1) assegna a pointer l'indirizzo
// di x (i.e., pointer punta x)
y = *pointer; // (2) assegna a y il contenuto di
// pointer
x = pointer; // (3) assegna ad x l'indirizzo
// contenuto in pointer
*pointer = 3; // (4) assegna alla variabile
// puntata da pointer il valore 3
Puntatori - Unità 6 2013/2014 Pagina 17
Diagramma della memoria (1/4)
32456 32456
x 1 x 1

54327 54327
y 2 (1) y 2

pointer = &x;
12098 12098
pointer ? pointer 32456

Puntatori - Unità 6 2013/2014 Pagina 18


Diagramma della memoria (2/4)
32456 32456
x 1 x 1

54327 54327
y 2 (2) y 1

y = *pointer;
12098 12098
pointer 32456 pointer 32456

Puntatori - Unità 6 201/2014 Pagina 19


Diagramma della memoria (3/4)
32456 32456
x 1 x 32456

54327 54327
y 1 (3) y 1

x = pointer;
12098 12098
pointer 32456 pointer 32456

Puntatori - Unità 6 2013/2014 Pagina 20


Diagramma della memoria (4/4)
32456 32456
x 32456 x 3

54327 54327
y 1 (4) y 1

*pointer = 3;
12098 12098
pointer 32456 pointer 32456

Puntatori - Unità 6 2013/2014 Pagina 21


Ricapitolando
Possibili valori ottenibili tramite l’utilizzo di
variabili puntatore:

pointer valore della variabile puntatore (i.e.,


l’indirizzo della locazione di memoria a cui punta)

&pointer indirizzo della locazione di


memoria del puntatore

*pointer valore contenuto nella locazione di


memoria indirizzata dal puntatore

Puntatori - Unità 6 2013/2014 Pagina 22


Operazioni sui puntatori
A valori di tipo puntatore si applicano le operazioni del
tipo int.
Particolarmente utile è l’operazione di incremento

int *pti;
...
pt++;

che consente di puntare alla successiva locazione di


tipo int.

L’uso di queste operazioni verrà approfondito nella


Unità 7 – Array e Matrici
Puntatori - Unità 6 2013/2014 Pagina 23
Puntatori costanti
La specifica const può essere applicata anche
a variabili di tipo puntatore.

double pi = 3.5;
double const *pt = &pi;
(*pt)++; // OK (pi vale 4.5)
pt++; // NO (pt e’ costante)

In questo caso, la specifica const si applica al


puntatore, ma non alla variabile puntata.

Puntatori - Unità 6 2013/2014 Pagina 24


Puntatori a costanti
Si può anche specificare che un puntatore debba
puntare a costanti.

const int k = 3;
const int *pt; // dichiarazione di puntatore
// a costante
pt = &k; // OK
pt++; // ammesso anche se non si sa cosa
// vada a puntare pt
int *pti;
pti = &k; // NO

Puntatori - Unità 6 2013/2014 Pagina 25


Puntatori a puntatori
Come per ogni altro tipo si può definire un
puntatore ad una variabile di tipo puntatore.
double x;
double * pt;
double ** ptpt; Cosa stampa questo
x = 4; frammento di codice?
pt = &x;
ptpt = &pt;
printf("%f\n", **ptpt);

Puntatori - Unità 6 2013/2014 Pagina 26


Diagramma della memoria
Stampa il valore di x.

Puntatori - Unità 6 2013/2014 Pagina 27


Il puntatore NULL (1/3)
Le variabili di tipo puntatore possono assumere
anche un valore speciale: NULL

Questo valore serve a specificare che la variabile


non punta alcuna locazione di memoria.

In C tale valore in genere corrisponde allo 0, ma


si raccomanda di usare NULL, in particolare per
verificare che ad una variabile puntatore non sia
associato
NULL è unauno specifico
costante riferimento.
simbolica in genere
definita in <stdio.h>
Puntatori - Unità 6 2013/2014 Pagina 28
Il puntatore NULL (2/3)
Si faccia attenzione a non confondere variabili
il cui valore è NULL con variabili non
inizializzate:
una variabile non inizializzata non ha alcun
valore, neanche NULL.
Il confronto con NULL può essere usato in una
condizione di un’istruzione if-else, for, etc.

Puntatori - Unità 6 2013/2014 Pagina 29


Il puntatore NULL (3/3)
Esempio
int *pt = NULL;
if (pt != NULL)
*pt = 10;

In questo caso il ramo if non viene eseguito.

L’istruzione *pt = 10; eseguita al momento


in cui pt vale NULL genererebbe un errore a
tempo di esecuzione.
Puntatori - Unità 6 2013/2014 Pagina 30
Il tipo void* (1/2)
Mentre nel caso delle dichiarazioni dei tipi primitivi è
indispensabile definire il tipo della variabile per
consentire al compilatore di allocare la memoria
necessaria, nel caso dei puntatori, la memoria per il
puntatore è fissa (corrisponde alla dimensione di un
indirizzo di memoria) e quindi si può omettere la
specifica del tipo della variabile puntata.

void *pt;
int i;
pt = &i;

Puntatori - Unità 6 2013/2014 Pagina 31


Il tipo void* (2/2)
In questi casi:

• non sono più ammesse le operazioni sui


puntatori

• il puntatore assegnato ad una variabile void*


non può essere assegnato ad una variabile di
tipo puntatore (ad un tipo definito).

Puntatori - Unità 6 2013/2014 Pagina 32


Conversioni sui puntatori

Anche nel caso delle variabili di tipo puntatore sono


possibili conversioni esplicite:

void * pt;
int i;
pt = &i;
int * pti;
pti = (int*)pt; // il valore è convertito
// a puntatore a int

Puntatori - Unità 6 2013/2014 Pagina 33


L’operatore sizeof (1/2)
Il numero di byte occupati da una variabile è dato
dall’applicazione di sizeof.

Esempio

sizeof(a) è il numero di byte occupati dalla variabile a

Puntatori - Unità 6 2013/2014 Pagina 34


L’operatore sizeof (2/2)
L’operatore sizeof può essere applicato ad un tipo, ad
un nome di variabile o ad una costante

Restituisce la dimensione in byte dell’oggetto passato


come parametro

• tale calcolo viene effettuato in compilazione in base al


tipo di dato che viene passato a sizeof

• se si incrementa un puntatore p, il suo valore numerico


(indirizzo in memoria in byte) verrà incrementato di
sizeof(*p)

Puntatori - Unità 6 2013/2014 Pagina 35


Stampa indirizzo, occupazione di
memoria e valore
int main (void) {
int a = 12;
char b = 'a';
float c = 0.1243;
printf ("Indirizzo di a e\' %x, occupa %d bytes,"
" il suo valore e\' %d\n", &a, sizeof(a), a);
printf ("Indirizzo di b e\' %x, occupa %d bytes,"
" il suo valore e\' %c\n", &b, sizeof(b), b);
printf ("Indirizzo di c e\' %x, occupa %d bytes,"
" il suo valore e\' %f\n", &c, sizeof(c), c);
return 0;
}

%x intero esadecimale senza segno


Puntatori - Unità 6 2013/2014 Pagina 36
Applicazione di sizeof (1/2)
Essendo un operatore, sizeof può essere utilizzato
ponendo l’operando tra parentesi oppure anche senza
l’utilizzo delle parentesi

Le seguenti istruzioni sono equivalenti tra loro:

char a = 'r';
int size_a;
size_a = sizeof a;
size_a = sizeof(a);

Puntatori - Unità 6 2013/2014 Pagina 37


Applicazione di sizeof (2/2)
Un’eccezione a questa possibilità si ha nel caso
seguente:

int size_float;

size_float = sizeof(float); // espressione


// valida

size_float = sizeof float; // errore in


// compilazione

quando l’operando di sizeof è il nome di un tipo di dato


(float, nell’esempio), le parentesi sono obbligatorie.
Puntatori - Unità 6 2013/2014 Pagina 38
Passaggio di parametri (1/2)
L’uso di variabili di tipo puntatore consente di
simulare la modalità di passaggio dei parametri
per riferimento.

Nel passaggio di parametri per valore, in genere


utilizzato per i dati di tipo primitivo, il parametro
formale può essere considerato come una
variabile locale che viene inizializzata al momento
della chiamata della funzione con il valore
corrispondente al parametro attuale.

Puntatori - Unità 6 2013/2014 Pagina 39


Passaggio di parametri (2/2)
Il passaggio di parametro per valore effettuato
con il tipo puntatore consente di aggirare una
proprietà cruciale del passaggio di parametri
per valore, cioè la garanzia che la funzione non
abbia effetti sul programma chiamante (ad
eccezione della restituzione del valore
calcolato).

Puntatori - Unità 6 2013/2014 Pagina 40


Esempio: passaggio parametri
puntatore (1/2)

void swap(int *a, int *b) {


int temp;
temp = *a;
*a = *b;
*b = temp;
}

Puntatori - Unità 6 2013/2014 Pagina 41


Esempio: passaggio parametri
puntatore (2/2)
int main (){
int x, y;
x = 12; y = 27;
printf("x = %d\n", x);
printf("y = %d\n", y);
swap(&x, &y);
printf("after swap\n");
printf("x = %d\n", x);
printf("y = %d\n", y);
}

Puntatori - Unità 6 2013/2014 Pagina 42


Esecuzione: passaggio parametri
puntatore

x = 12
y = 27
after swap
x = 27
y = 12

Puntatori - Unità 6 2013/2014 Pagina 43


Passaggio parametri puntatore
Nonostante il passaggio di parametri sia per valore, il
risultato dell’esecuzione della funzione swap consiste
proprio nel modificare il valore di due variabili del
programma principale.

Passando alla funzione i puntatori a due variabili è


possibile modificare il valore delle variabili puntate,
anche se il valore delle variabili puntatore rimane
inalterato.

L’uso dei puntatori nel passaggio di parametri per valore


consente di effettuare in pratica il passaggio di parametri
per riferimento.
Puntatori - Unità 6 2013/2014 Pagina 44
Passaggio per valore VS passaggio
per valore puntatore
void swap(int *a, int *b) {
int temp; swap(&x, &y);
temp = *a;
definizione invocazione
*a = *b;
*b = temp;
}

void swapVal(int a, int b) {


int temp; swapVal(x, y);
temp = a;
definizione invocazione
a = b;
b = temp;
}

Puntatori - Unità 6 2013/2014 Pagina 45


Esempio: intestazione di funzione con
parametri puntatore

void f(int* param1, double* param2);

Parametro Parametro
formale formale
di tipo puntatore di tipo puntatore
ad int a double

Puntatori - Unità 6 2013/2014 Pagina 46


Chiamata con parametri puntatore:
funzione chiamante
Quando la funzione viene chiamata, devono essere
passati come parametri gli indirizzi delle variabili

•  Il passaggio degli indirizzi potrà essere ottenuto


applicando l’operatore di indirizzo & alla variabile che
deve essere modificata

•  Sono passate le “celle” dove i valori sono


memorizzati &a
a 2 int a; double b;
a = 2; b = 3.2;
&b f(&a, &b);
b 3.2
Puntatori - Unità 6 2013/2014 Pagina 47
Chiamata con parametri puntatore:
funzione chiamata
Nell’intestazione della funzione chiamata deve
essere usato l’operatore di deriferimento per le
variabili passate per indirizzo

&pa void f(int *pa, double *pb)


pa &a {
*pa = 6;
&pb
*pb = 7.4;
pb &b }

Puntatori - Unità 6 2013/2014 Pagina 48


Chiamata con parametri puntatore:
esecuzione
int a; double b;
&a
a = 2; b = 3.2;
a 6 f(&a, &b);
&b
&pa
b 7.4 int *pa = &a;
pa

void f(int *pa, int *pb)


&pb {
pb *pa = 6;
*pb = 7.4;
}
Puntatori - Unità 6 2013/2014 Pagina 49
Vantaggi del passaggio di parametri
puntatore
Il passaggio di parametri di tipo puntatore è
particolarmente utile quando i dati che devono
essere scambiati tra funzione chiamata e
programma chiamante sono voluminosi.

Il passaggio di un puntatore risulta molto più


efficiente sia in termini di occupazione di
memoria che di tempo di calcolo (infatti,
occorre la copia del solo puntatore ai dati, non
di tutto l’insieme dei dati).

Puntatori - Unità 6 2013/2014 Pagina 50


Valori restituiti di tipo puntatore
double* puntatore(double a) {
double *r = malloc(sizeof(double));
*r = a;
return r;
}

int main () {
double *pd = puntatore(5.4);
printf("pd = %p\n", pd);
printf("*pd = %f\n", *pd);
return 0; la funzione puntatore crea un puntatore ad
} una variabile di tipo double e la inizializza
con il valore passato come argomento
Puntatori - Unità 6 2013/2014 Pagina 51