Attenzione: questa pagina è stata aggiornata, vedere: gestione_della_memoria

Puntatori a... (qualcosa)

Le variabili automatiche (cosiddette locali) sono gestite automaticamente nello stack e quando non servono più vengono automaticamente “distrutte”.

vedere code_data_bss

Durante l'esecuzione del programma può essere necessario allocare (riservare) dinamicamente della memoria. Questo accade per le operazioni dove non si possono prevedere in anticipo le richieste di un utente. Per riservare una zona di memoria dinamicamente si usa l'operatore new.

new float; // riserva la quantità di memoria necessaria per un numero in virgola mobile

L'operatore new restituisce l'indirizzo iniziale della memoria riservata. Quale tipo di dato è utile per memorizzare un indirizzo di memoria? Anche se un indirizzo di memoria è un numero, non si usa il tipo int, ma un tipo specifico chiamato puntatore a…. Nel precedente esempio si deve usare un “puntatore a float”. Per la sua dichiarazione basta aggiungere l'operatore asterisco * dopo il nome del tipo:

float* numero;       //definizione della variabile puntatore a...
numero = new float;  // inizializzazione della variabile puntatore a..

Nel precedente esempio il programma ha riservato due aree di memoria:

  • “numero” come variabile automatica, per contenere un indirizzo
  • “new float” come variabile dinamica, per contenere un numero in virgola mobile

Le due operazioni possono essere eseguite anche in un solo colpo:

float* numero = new float;

La seconda variabile (nell'heap) sarebbe inutilizzabile senza la prima (nello stack), ma chi è che controlla la “distruzione” della prima? Poiché “numero” è una variabile automatica essa si libera automaticamente quando termina la funzione che la contiene, quindi l'area allocata dinamicamente potrebbe diventare inutilizzabile nel caso in cui questo accadesse. Nell'esempio visto, il programmatore deve usare delete prima che si verifichi questo evento “disastroso”:

delete numero;

Riassumendo, questo è l'ordine in cui si devono fare le cose

  • inizia la funzione
    1. definire un puntatore (nella mem. automatica)
    2. riservare la memoria dinamica con new e inizializzare il puntatore
    3. utilizzare a proprio piacere l'area dinamica…
    4. liberare la memoria dinamica con delete
  • termina la funzione (si libera anche il puntatore)

Oppure, in altre parole, ricordarsi di liberare tutte le variabili dinamiche create per evitare problemi di gestione della memoria o falle (memory leak). I puntatori quindi sono variabili usate prevalentemente per controllare la memoria dinamica (heap).

Un consiglio

L'utilizzo dei puntatori è un comune causa di errori o bug nei programmi, soprattutto se questi sono utilizzati per un lungo tempo. La strategia consigliata è di usare l'operatore new solo nei costruttori e l'operatore delete solo nei distruttori.

I puntatori e gli array lavorano sulla memoria a basso livello, cioè senza controlli, causando oscuri malfunzionamenti e bug! I vector invece hanno un'efficenza ancora sufficientemente elevata e sono più facili da usare (ci sono più controlli).

Creando un vector (o uno fstream) come oggetto locale, la distruzione sarà eseguita automaticamente al termine della funzione e così si posso evitare i puntatori. In ogni caso, non usare direttamente new e delete, ma creare oggetti con new nel costruttore e delete nel distruttore.

La memoria dinamica, rispetto alla memoria automatica, può essere utile per contenere oggetti molto grandi o di dimensioni non note a priori, oppure per contenere oggetti che devono durare a lungo (più della durata degli oggetti locali (automatici) di una funzione).

I puntatori non vengono usati solo per accedere alla memoria heap. A volte i puntatori permettono di accedere anche a zone della memoria (stack) altrimenti inaccessibili, come quando si vuole che una funzione conosca l'indirizzo di una variabile usata da un'altra per poterla modificare. Ma questo utilizzo non dovrebbe essere necessario e probabilmente si basa su un cattivo stile di programmazione. A volte i puntatori permettono di puntare alle funzioni, consentendo di eseguire funzioni diverse per condizioni diverse che si possono verificare in tempo di esecuzione (running time), in altre parole i puntatori consentono anche il polimorfismo. http://www.illuminamente.org/dokuwiki/doku.php?id=appunti3s:stream#file

int a1[5];

In linguaggio C scrivere “a1” equivale a scrivere l'indirizzo del primo elemento dell'array “&a1[0]” , quindi, quando si passa un array ad una funzione, in realtà si passa sempre il valore del suo indirizzo (come un puntatore).

La differenza tra un puntatore (int p) è l'indirizzo (&a1[0]) è che il primo può cambiare e puntare qualsiasi area di memoria, mentre l'indirizzo del primo elemento di un array non può essere cambiato.

int *p = a1

Se il puntatore punta all'area di memoria di un array, si può utilizzare una sintassi simile a quella di un array:

p[0]=3; 

Nelle dichiarazioni delle funzioni si può usare equivalentemente una qualsiasidelle seguenti due sintassi:rif.standard 13.1-3.3

int fun(int p[]);
int fun(int *p);

differenza tra

return &variabile; // (dangling pointer) resituisce l'indirizzo di una 
                   // var che si autodistrugge(locale,automatica)
return puntatore; // restituisce un indirizzo (la copia del contenuto di 
                  // un puntatore) che contiene l'indirizzo di (si spera) 
                  // un'area allocata con new, che sopravvive alla funzione.

È introdotto nel linguaggio C++ per necessità. Ovviamente non esistono oggetti void. Il tipo void viene usato solo per indicare “nessun valore restituito” da una funzione.

Il puntatore void* è usato solo per poter puntare qualsiasi cosa, che il programma non sa cosa sia (mentre il programmatore deve sapere cosa e'). Il tipo void* serve solo per copiare un indirizzo di memoria a basso livello e non consente di lavorare sul contenuto della memoria.

Questo significa che per poter lavorare sul contenuto bisogna effettuare un casting verso un altro tipo di puntatore. Mentre il casting tra altri tipi può essere implicito, per i puntatori è necessario usare il casting esplicito… Mentre esistono conversioni implicite tra float e int, non esiste tra float* e int*

  • appunti3s/pointers.txt
  • Last modified: 2019/06/29 15:33
  • by profpro