Sei sulla pagina 1di 4

I PUNTATORI

Il puntatore è un costrutto di basso livello, quindi che opera secondo il linguaggio macchina,
attraverso cui accediamo alla memoria tramite i suoi indirizzi, a differenza delle variabili, con
cui si accede tramite nomi o identificatori.

Il linguaggio C++ permette di accedere in maniera diretta ed esplicita all’indirizzo di una


variabile tramite l’operatore di riferimento o reference, “&” [int x --- &x] . Gli indirizzi di
memoria possono essere assegnati a delle variabili definite puntatori, i quali richiedono il tipo
del dato puntato e il simbolo “*” per essere definiti [int *y] .

Le operazioni

Con i puntatori è possibile svolgere delle assegnazioni e accedere alla variabile puntata, in
lettura e in scrittura, attraverso l’operatore di indirezione o dereference “*” . Non è invece
possibile compiere assegnazioni tra puntatori di tipo diverso a causa dello spazio differente
occupato in memoria e della differente rappresentazione del dato.

Il puntatore NULL

Ogni puntatore deve essere inizializzato in quanto, altrimenti, punterebbe ad un indirizzo


casuale della memoria. Per questo scopo è stato introdotto il tipo “NULL” per i puntatori, di
modo che si potesse inizializzarli senza assegnarvi alcun indirizzo [int *y = NULL] .

Puntatori ed array

Il puntatore e l’array sono due concetti molto vicini. E’ possibile dire che l’array è un
puntatore che punta ad un valore specifico, il quale a sua volta punta ad n valori ; mentre è
anche possibile affermare che un array sia un puntatore costante poiché non può variare gli
elementi a cui punta.

Operazioni aritmetiche

Nell’ambito dei puntatori in relazione agli interi sono ammesse le seguenti operazioni
matematiche :

 Somma di un puntatore ed un intero : il risultato è l’indirizzo seguente in memoria ;


 differenza di un intero da un puntatore : il risultato è l’indirizzo precedente in memoria ;
 somma o differenza tra due puntatori : il risultato è il valore del puntatore incrementato o
decrementato del prodotto tra l’altro puntatore e il numero di byte da lui occupati.

Considerando la relazione tra puntatori e array, l’aritmetica a noi disponibile diviene più
ampia, sarà infatti possibile svolgere operazioni di :

 Autoincremento (++) : ha una precedenza maggiore sull’operatore in direzionale, tuttavia


assume un comportamento diverso se usato come suffisso. L’espressione * (y++) equivarrà
a *y , y++ .
 Decremento (--) : ha lo stesso comportamento dell’incremento, ma ovviamente effetti
diversi.
Puntatori e costanti

Possiamo definire dei puntatori a costanti in modo tale che, seppur usando l’indirezione, la
variabile puntata non possa essere in alcun modo modificata. La dichiarazione prevede
l’utilizzo dell’espressione “const” come prefisso alla consueta dichiarazione.

[ const int *yx ]

Puntatori e funzioni

Possiamo utilizzare i puntatori anche come argomento di una funzione, passando un


riferimento o il valore di un puntatore. Ne segue che le operazioni rimangono invariati con la
sola differenza dell’esplicitazione dell’indirezione, cioè il simbolo *. L’utilizzo vantaggioso lo si
constata nel lavoro con gli array, in quanto essi sono parametri formali normalmente gestiti
come puntatori.

Nel linguaggio C++, lavorando con i puntatori come parametri, non si considera l’indirizzo di
una variabile bensì l’indirizzo corrispondente alla prima istruzione eseguita dalla funzione. La
definizione standard per una funzione avente un puntatore come parametro è :

int (*y) (int x , int z) ---- tipo (puntatore) (argomenti)

Per richiamare una funzione avente un puntatore come argomento, invece, è sufficiente
utilizzare il puntatore seguito dai parametri da passare :

somma = (*y) (x , z)

Puntatori e strutture (struct)

E’ possibile utilizzare dei puntatori anche in ambito delle variabili struct o struttura. L’unica
difficoltà e particolarità consiste nell’accesso ai campi della struttura a causa degli operatori,
esso può avvenire in due modi :

 Tramite l’utilizzo delle parentesi () in quanto l’operatore “.” ha la precedenza


sull’operatore di indirezione. Si ha dunque : (*puntatore).struct
 Tramite l’utilizzo dell’operatore specifico “” : puntatore  struct
La memoria e l’allocazione dinamica

Come detto, i puntatori consentono la manipolazione e l’accesso agli indirizzi di memoria,


tuttavia, affinché si possa comprendere il loro funzionamento è necessario definire la
struttura della memoria del calcolatore. Essa, infatti, è costituita da celle contenenti un
numero preciso di bit e a cui è associato un indirizzo di memoria specifico. In maniera
automatica, il sistema operativo segmenta la memoria assegnando delle aree diverse ad ogni
programma e ai rispettivi dati manipolati.

Lo stack

Struttura dati tipo lista gestita attraverso la modalità Last In First Out (LIFO) , secondo cui
l’ultimo elemento inserito è anche il primo ad essere rimosso dalla memoria. Esso possiede
due operazioni principali implementate dal processore : il push (aggiunta di un elemento in
cima) e il pop (rimozione di un elemento dalla cima) . Lo stack possiede un registro chiamato
stack pointer (SP) puntante alla cima degli indirizzi dello stack in se e la cui dimensione è
automaticamente adattata dal kernel.

Nel contesto del programma, gli elementi dello stack sono definiti stack frame e contengono le
loro variabili all’interno delle parentesi graffe. Durante il processo di esecuzione, l’elemento in
testa diviene l’istruzione da eseguire mentre i successivi degli elementi esterni. In questo
modo le variabili sono definite e valide soltanto nel loro ambito di validità e visibilità , in
seguito vengono cancellate liberando memoria divenendo automatiche.

L’heap

L’heap è una struttura dati utilizzata per l’allocazione dinamica e consiste, a livello logico, in
un albero binario che soddisfa la proprietà di heap, di modo che il dato A, genitore di B, abbia
un valore (o chiave) ordinato rispetto a B in relazione all’ordine applicato nell’heap.

Nonostante lo stack sia di per sé efficiente e veloce nelle operazioni di allocazione, ha una
dimensione limitata, seppur variabile in base al linguaggio e all’architettura del sistema. Per
questo motivo si possono verificare degli errori di stack overflow, i quali causano l’immediata
interruzione del programma ; per ovviare a ciò viene utilizzato l’heap con l’ausilio dei
puntatori :

Esempio :

#include <iostream>
int main() {
int* var ; var = new int ;
delete var ;
return 0 ;
}
Tramite quest’esempio è possibile comprendere il processo di allocazione dinamica, la prima
istruzione crea la variabile “var” nello stack ; la seconda assegna il valore di “var” al primo
indirizzo di memoria heap disponibile ed idoneo ; la terza istruzione elimina “var” liberando
la memoria in maniera dinamica quando la variabile non è più utile.
Pur essendo ogni variabile dello stack automatica, inclusi i puntatori, non è detto che le celle
di memoria puntate appartengano alla gestione dello stack. In situazioni di questo tipo si
verifica un errore definito memory leak , la completa perdita e impossibilità d’uso di un
indirizzo di memoria puntato da un puntatore. Per evitare questo tipo di errori è sempre
consigliato liberare esplicitamente la memoria heap.

Un ulteriore errore comune consiste nella saturazione dell’heap, la quale causa una serie di
risultati nulli e quindi non validi. Nelle versioni del protocollo standard c++11 il valore
identificativo di quest’errore è nullptr.

BSS segment

La Block Started Symbol è una sezione di memoria contenente delle variabili inizializzate a
zero, e quindi modificabili. La sua estensione è definita dal sistema e non varia in esecuzione.

Data segment

Il Data Segment è una sezione di memoria contenente dati inizializzati come variabili statiche
di tipo globale o locale. La sua estensione è definita dal sistema precedentemente e non varia
durante l’esecuzione.

Read-only data segment

Il Read Only Data Segment è una sezione di memoria in cui vi sono le istruzioni eseguibili e le
costanti. La sua dimensione rimane fissa durante l’esecuzione ma viene precedentemente
definita.

Code segment

Il Code o Text Segment è una sezione di memoria in cui vi sono le istruzioni eseguibili e le
costanti. La sua dimensione rimane fissa durante l’esecuzione ma viene precedentemente
definita.

Potrebbero piacerti anche