Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
I corsi di Ingegneria
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Valeria Vittorini » 4.Puntatori a tipi di dato strutturati. Allocazione Dinamica


I puntatori a dati strutturati

Un puntatore può puntare a variabili strutturate (di tipo array o record). Esamineremo i seguenti casi:

  • puntatore ad array;
  • puntatore a record.

Tra i casi elencati, il puntatore ad array può utilmente essere sostituito dalla notazione a[i] tipica degli array

I puntatori ad array

Un puntatore ad un array a è una variabile che punta alla prima locazione dell’array (a[0] in C++);

considerando che i successivi elementi dell’array sono allocati in posizioni contigue, esso consente di puntare anche a tutti gli altri elementi dell’array.

T a[dim]; // a è un array di dim elementi di tipo T
T* p ; // p è un puntatore a T
p=a;p=&a[0];
Un puntatore ad array è dunque di per sé un puntatore al tipo T degli elementi dell’array

I puntatori ad array: esempio

const int dim=100;
float a [dim]; //
a è un array di dim elementi di tipo float

//azzeramento di tutti gli elementi di un array di 100 elementi
for (float* p = a, int i=0; i<dim; i++, p++)
*p= 0.0;

Se p e q sono due puntatori allo stesso array a, p punta all’elemento a[i] e q all’elemento a[j], la differenza p-q fornisce la “distanza” tra i due elementi.

Puntatori a record

Un puntatore ad un record è una variabile che punta all’indirizzo di memoria ove il record è allocato;
esso è molto utile nella realizzazione dei tipi dinamici

// Dichiarazioni di un tipo strutturato Ts
struct Ts { // Ts è un tipo strutturato

D1;
.....;
Dn ; };

Ts r ; // r è variabile di tipo Ts

La funzione di accesso al singolo campo del record è l’operatore punto (.). Per accedere al campo D1 della variabile r la sintassi è: r.D1

// Dichiarazione di variabile di tipo puntatore a Ts
Ts* p;

Puntatori a record

Ts* p = &r; // p è una variabile puntatore inizializzata ad r

p può essere ridefinito nel corso del programma

p= &r1; //ora p punta alla variabile r1 di tipo Ts

Accesso alla singola componente di un record attraverso il puntatore a record:

(*p).Di;p->Di;

Array di puntatori

  • Gli elementi di un array possono essere di tipo puntatore.
  • Un caso comune è costituito da un array di stringhe, data la stretta relazione in C/C++ tra una stringa e il puntatore al suo primo carattere.

Esempio

  • “Anno” è un vettore di 12 elementi ciascuno dei quali è di tipo puntatore a carattere (char *).
  • I 12 valori utilizzati per inizializzare il vettore corrispondono ai nomi del mese dell’anno, ognuno di essi è una stringa terminata da ‘\0′ la cui lunghezza è determinata dal numero di caratteri presenti in essa.
  • Sebbene possa quindi sembrare che siano le stringhe ad essere memorizzate nell’array, in realtà l’array contiene solo 12 puntatori.
Mostra codice

Variabili e strutture dinamiche

La gestione delle variabili dinamiche è resa possibile dalla disponibilità dei seguenti meccanismi linguistici:

  • variabili di tipo puntatore;
  • operatore new;
  • operatore delete.

Operatore new

T* p = new T; //alloca memoria sufficiente a

contenere un // oggetto di tipo T e

restituisce puntatore

T* p1 =new T[n]; //alloca memoria sufficiente a

contenere n // elementi di tipo T e

restituisce puntatore

In fase di esecuzione, new alloca un’area di memoria sufficiente ad ospitare un valore del tipo T e restituisce il puntatore a tale area.

Il tipo T definisce implicitamente l’ampiezza dell’area di memoria occorrente.

Operatore delete

delete p; //dealloca area puntata da p

delete [] p1; //dealloca tutto l’array precedentemente allocato

Produce la deallocazione dell’area di memoria puntata dalla variabile p, cioè annulla l’allocazione, rendendo nuovamente disponibile lo spazio di memoria prima occupato.

Leak di memoria (Memory Leak)

  • Quando una variabile allocata dinamicamente non serve più (il suo ciclo di vita si è esaurito all’interno del programma) deve essere distrutta dal programmatore mediante l’operatore delete.
  • Se questa operazione non viene effettuata, l’area di memoria heap da essa occupata in memoria non viene rilasciata fino al termine dell’esecuzione del programma.
  • Se poi il puntatore che puntava a tale area viene assegnato ad un altro indirizzo, l’area occupata risulta irraggiungibile.
  • Si avrà quindi uno spazio in memoria non utilizzato o non più utilizzabile.
  • Questo fenomeno è noto con l’espressione memory leak.

Dangling reference

  • Una volta rilasciata la memoria mediante l’operatore delete il puntatore che si riferiva a quell’area di memoria continua a contenere l’indirizzo relativo.
  • Pertanto è necessario assegnare tale puntatore a 0 o assegnarlo ad un indirizzo valido per evitare il fenomeno noto come dangling reference: cioè la presenza di puntatori che erroneamente si riferiscono ad indirizzi di memoria che sono stati rilasciati e di cui quindi il programmatore non ha più il controllo.

Allocazione della memoria


Allocazione delle variabili dinamiche

Il puntatore p è una variabile automatica, quindi allocata in area stack. Dopo l’istruzione new, p punta ad una locazione nello heap atta a contenere un intero.

Il puntatore p è una variabile automatica, quindi allocata in area stack. Dopo l'istruzione new, p punta ad una locazione nello heap atta a contenere un intero.


Allocazione dinamica di un vettore

Con l’allocazione dinamica di un vettore il puntatore restituito è quello all’indirizzo del primo elemento del vettore, pertanto è di tipo T* se T è il tipo degli elementi del vettore

float* a= new float[n];

Il riferimento agli elementi del vettore viene espresso a mezzo della consueta notazione con indice

a[i] = 10.0 ⇔ *(a+i)=10.0

delete [] a; //cancella l’intero vettore precedentemente allocato

Esempio 1

Mostra codice

Allocazione dinamica di una stringa

  • Con l’allocazione dinamica di una stringa il puntatore restituito è quello all’indirizzo del primo elemento della stringa. Pertanto è di tipo char *

char* s= new char[n+1];

  • L’istruzione precedente alloca spazio per una stringa di n caratteri più il terminatore.
  • Valgono le stesse considerazioni fatte per i vettori.

Esempio 2

Un programma che alloca spazio in area heap per una stringa di n caratteri e la inizializza con dati letti da tastiera.

Mostra codice

Allocazione dinamica di un record

L’allocazione dinamica di un record avviene analogamente attraverso l’uso dell’operatore new

Indicato con R un tipo record e con r un puntatore ad R, si ha:

R* r = new R;

Esempio 3

Dichiarazione di un tipo (record) Punto, allocazione dinamica di una variabile di tipo Punto e sua inizializzazione.

Mostra codice

Un caso particolare di assegnazione tra variabili ….

  • Definito un tipo record “Persona” si vuole eseguire il seguente programma:
Mostra codice

Due diverse definizioni di “Persona”


…in esecuzione

Output ottenuto nel caso della Definizione 1

Mostra codice

Output ottenuto nel caso della Definizione 2

Mostra codice

La funzione Inizializza_Persona

  • Nel caso della Definizione 1

void Inizializza_Persona(Persona & P) {

cout << "\n inserire Nome e Cognome: ";
cin.getline(P.Nome, dim-1);
cout << "\n inserire l'eta': "; cin >> P.eta;

}

  • Nel caso della Definizione 2

void Inizializza_Persona(Persona & P) {

char Buffer[dim];

cout << "\n inserire Nome e Cognome: ";
cin.getline(Buffer, dim-1);
P.Nome=new char [strlen(Buffer)+1];
strcpy(P.Nome,Buffer);
cout << "\n inserire l'eta': "; cin >> P.eta;

}

Allocazione dinamica di una variabile puntatore

  • Una variabile di tipo puntatore appartiene – come ogni altra variabile – ad una classe di memorizzazione che ne definisce la visibilità e stabilisce dove la variabile viene allocata.
  • Una variabile puntatore può essere pertanto in generale allocata in area stack, in area dati statici, in area heap ed anche essere dichiarata costante.

Esempio 4

Allocazione dinamica di una variabile di tipo puntatore ed inizializzazione dell’area allocata

Mostra codice

Allocazione dinamica di un array bidimensionale

  • Per allocare dinamicamente una matrice è necessario allocare una struttura bidimensionale. Un modo semplice per capire è vedere la matrice nxm come un vettore di n elementi ciascuno dei quali è a sua volta un un vettore di m elementi.
  • Per allocare dinamicamente la matrice è dunque necessario allocare dinamicamente sia il vettore di n elementi sia gli n vettori di m elementi. Il vettore di n elementi contiene i puntatori agli n vettori di m elementi.
  • Il puntatore alla struttura complessiva è un puntatore al vettore di n elementi, I cui elementi sono puntatori. Questo puntatore è quindi un puntatore a puntatore! Se il tipo degli elementi della matrice è T, il puntatore alla matrice è di tipo T** e punta al primo elemento della matrice linearizzata.

Esercizio: Allocazione dinamica di una matrice di interi

  • Supponiamo di voler allocare dinamicamente la matrice A di numeri interi di dimensioni nxm.
  • Inizialmente bisogna allocare il vettore che conterrà i puntatori alle n righe. Il puntatore restituito da new è il puntatore utilizzato per accedere alla matrice.

int** A= new int*[n];

  • Quindi bisogna allocare gli n vettori che sono le righe della matrice. Ogni puntatore restituito da new è un elemento del vettore puntato da A.

for (int i=0; i<n; i++)

A[i]= new int[m];

Deallocazione di un array bidimensionale allocato dinamicamente

  • Bisogna deallocare tutta la struttura dati allocata, procedendo in maniera inversa rispetto alla sua costruzione.
  • Quindi bisogna deallocare prima le righe della matrice e poi il vettore di puntatori.

for (int i=0; i<n; i++) // dealloca gli n vettori che sono le righe della matrice

delete [] A[i];

delete [] A; // dealloca il vettore di puntatori alle righe

Mostra codice

Errori comuni con puntatori e variabili dinamiche

Tipici errori quando si usa l’allocazione dinamica sono:

  • Dimenticare di allocare un dato nello heap e usare il puntatore come se lo stesse riferendo (produce un effetto impredicibile).
  • Dimenticare di “restituire” la memoria allocata quando non serve più (rischio di memory leak).
  • Dimenticare di riassegnare ad un valore valido puntatori che puntavono ad aree di memoria ormai rilasciate (dangling reference).
  • Tentare di usare dati dinamici dopo che sono stati deallocati.

I materiali di supporto della lezione

C. Savy: Da C++ ad UML, McGraw-Hill, Cap. 8, par. 8.3.1, 8.3.5, 8.4

Da Fondamenti di Informatica II, Parte II, Capitolo VII, §6.1, §6.2

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion