Indirizzi delle variabili
A ogni variabile sono associati tre concetti fondamentali:
 il valore memorizzato;
 il tipo dati di appartenenza;
 l’indirizzo.
Il valore memorizzato in una variabile è detto anche il suo contenuto, o
rvalue.
Il tipo dati di appartenenza è dichiarato usando un’istruzione di
dicharazione.
L’indirizzo, detto anche lvalue, è quello della prima locazione di
memoria usata per la variabile.
Come abbiamo visto, il numero delle locazioni di memoria
effettivamente usate da una variabile dipende dal suo tipo dati.
Quindi, quando si dichiara una variabile con una istruzione quale
int numero
il compilatore tiene traccia di ciò creando (o aggiornando) in memoria
una tabella del tipo:
Il campo INDIRIZZO viene riempito automaticamente dal
compilatore, il campo VALORE viene riempito dall’utente con una
istruzione di assegnazione.
Come indica la figura, l’indirizzo di una variabile è scritto di solito alla
sua sinistra, il suo contenuto o valore alla destra.
Operatore di indirizzo &. Di solito ci si interessa più al valore
assegnato a una variabile (il suo contenuto, o rvalue), e meno a dove
il valore sia memorizzato (il suo indrizzo, o lvalue).
Tuttavia il C consente di visualizzare in modo semplice l’indirizzo di una
variabile (ossia quello del primo byte che le viene riservato nella
memoria del computer).
Per indicare tale indirizzo si usa l’operatore di indirizzo &, che si legge
l’indirizzo di, subito prima del nome della variabile (senza
spazi intermedi). Ad es.:
L’operatore & è un operatore unario, che restituisce l’indirizzo del
suo operando.
Sequenza di controllo di conversione %p. Per visualizzare con
printf() l‘indirizzo di una variabile preceduta dall’operatore di
indirizzo & si usa la sequenza di controllo di conversione %p,
come nel programma seguente.
#include <stdio.h>
void main(void)
{
int numero;
numero = 22;
printf(“numero = %d L’indirizzo di numero è %p.”,
numero, &numero);
}
Esso produce la seguente uscita
numero = 22 L’indirizzo di numero è 0012FF7C.
Ogni volta che il programma viene eseguito, esso visualizza (in
formato esadecimale) l’indirizzo del primo byte usato per
memorizzare la variabile numero. Tale indirizzo può cambiare,
ovviamente, a ogni esecuzione del programma.
La figura seguente illustra l’informazione d’indirizzo aggiuntiva fornita
dall’uscita del programma
Come vedremo, usare gli indirizzi anziché semplicemente visualizzarli
fornisce un potente strumento di programmazione: gli indirizzi
consentono di entrare direttamente nella operatività interna del
computer e di accedere alla sua struttura fondamentale di memoria.
Ciò fornisce al programmatore C una possibilità e una potenza di
programmazione non disponibili nella maggior parte degli altri
linguaggi per computer.
Puntatori
Oltre a visualizzare l’indirizzo di una variabile, come fa il programma
precedente, è anche possibile memorizzare tale indirizzo in una
variabile precedentemente dichiarata.
Ad es., l’istruzione di assegnazione
ind_numero = &numero;
memorizza nella variabile ind_numero l’indirizzo della variabile
numero, come indicato in figura.
La variabile ind_numero, in quanto contiene l’indirizzo di un’altra
variabile, è detta variabile puntatore o puntatore. Pertanto:
i puntatori sono variabili usate per
memorizzare gli indirizzi di altre variabili.
L’esecuzione della precedente istruzione di dichiarazione
ind_numero = &numero; aggiornerebbe la precedente tabella
come indica la figura:
Analogamente,
come indica la seguente tabella:
Anche le variabili d, punt_tab e punt_car sono puntatori.
Operatore di indirezione *. Per usare un indirizzo memorizzato, C
fornisce l’operatore di indirezione * (detto anche di deriferimento
o di risoluzione del riferimento).
Il simbolo *, seguito immediatamente da un puntatore (senza spazi
intermedi), significa
la variabile il cui indirizzo è memorizzato in
Così, se ind_numero è un puntatore, la scrittura
Anche * è un operatore unario, che restituisce il valore dell’oggetto
puntato dal suo operando (ossia da un puntatore).
Quindi, per visualizzare il contenuto della variabile numero (ossia
l’intero 22), si può scrivere indifferentemente:
• l’istruzione
printf(“%d”, numero);
che si
legge
stampa il contenuto
della variabile numero
• oppure la coppia di istruzioni
ind_numero = &numero;
printf(“%d”, *ind_numero);
che si
leggono
stampa il contenuto della
variabile il cui indirizzo è
memorizzato nella
variabile ind_numero
Usando un puntatore (ind_numero), il contenuto di numero si
ottiene leggendo dapprima il contenuto del puntatore, che è
l’indirizzo di numero, quindi usando tale indirizzo per ottenere il
contenuto di numero.
Pertanto l’uso di un puntatore fa eseguire al computer un doppio
passaggio: dapprima richiama l’indirizzo di un dato, quindi lo usa
per richiamare il dato effettivo.
Questo è un modo indiretto per ottenere il valore finale, e in effetti
questa procedura è detta indirizzamento indiretto. La sua utilità sarà
vista più avanti.
Dichiarazione dei puntatori. Come tutte le variabili, anche i puntatori
devono essere dichiarati prima di essere usati.
Nel dichiarare un puntatore, il C richiede che sia indicato il tipo dati
della variabile puntata.
Ad es., se l’indirizzo memorizzato nel puntatore ind_numero è quello
di un intero, la dichiarazione corretta del puntatore è:
(quindi la dichiarazione viene letta “all’indietro”).
Si osservi che la precedente dichiarazione specifica anche che
ind_numero deve essere un puntatore (dato che è usato con
l’operatore di indirezione *).
Consideriamo il programma seguente:
La sua uscita è:
L’indirizzo memorizzato in ind_num è 0012FF78
Il valore puntato da ind_num è 22
L’indirizzo ora memorizzato in ind_num è 002FF7C
Il valore ora puntato da ind_num è 158
La sola utilità di questo programma è di aiutarci a capire “cosa è
memorizzato dove”.
Sarebbe stato certamente più semplice se il puntatore del programma
avesse potuto essere dichiarato come point ind_num;.
Tuttavia, tale dichiarazione non conterrebbe informazioni sulla memoria
usata dalla variabile il cui indirizzo è memorizzato in ind_num.
Questa informazione è essenziale quando il puntatore è usato con
l’operatore di indirezione, come avviene nella seconda chiamata a
printf() nel programma precedente. Infatti
 se in ind_num è memorizzato l’indirizzo di un carattere, quando si
usa l’indirizzo viene richiamato 1 byte di memoria;
 se in ind_num è memorizzato l’indirizzo di un intero, vengono
richiamati 2 byte di memoria, mentre
 se in ind_num è memorizzato l’indirizzo di un numero in virgola
mobile, vengono richiamati 4 byte.
È per questa ragione che la dichiarazione di un puntatore deve
comprendere il tipo di variabile che viene puntata.
I due operatori & e * sono l’uno il complemento dell’altro; qualora
fossero applicati consecutivamente a ind_a, in qualsiasi ordine, il
loro effetto si “annullerebbe”, come mostra il seguente programma:
#include <stdio.h>
void main(void)
{
int a, *ind_a;
a = 7;
ind_a = &a;
printf("\n &a = %p\n", &a);
printf("Prova che * e & sono complementari:\n
&*ind_a = %p\n *&ind_a = %p\n", &*ind_a, *&ind_a);
}
Esso produce la seguente uscita:
&a = 0012FF7C
Prova che * e & sono complementari:
&*ind_a = 0012FF7C
*&ind_a = 0012FF7C
Come si chiamava il padre della protagonista di questa famosa tragedia?
Codice ASCII di un carattere (%o, %x). Dato che il C non possiede
un operatore specifico che fornisca il codice ASCII di un carattere,
esso si può ottenere semplicemente:
• memorizzando il carattere in una variabile;
• stampando il “valore” della variabile, ossia stampando la variabile
tramite la sequenza di controllo di formato %d.
Ciò si può ottenere con il programma seguente:
#include <stdio.h>
void main(void)
{
char var = 'a';
printf("\nIl codice ASCII decimale di %c è %d.",var,var);
}
che produce l’uscita
Il codice ASCII decimale di a è 97.
Naturalmente il codice ASCII può essere ottenuto anche in formato
ottale o esadecimale, sostituendo la sequenza di controllo di formato
%d rispettivamente con %o o con %x.
Il programma seguente stampa l’indirizzo di una variabile di tipo
carattere, il valore contenuto in tale indirizzo e il codice ASCII della
variabile usando un puntatore.
#include <stdio.h>
void main(void)
{
char *ind_var;
char var = 'a';
ind_var = &var;
printf("Il valore memorizzato in ind_var è: %p", ind_var);
printf("\nIl valore puntato da ind_var è: %c", *ind_var);
printf("\nIl codice ASCII di %c è %x", var, *ind_var);
}
Ecco l’uscita prodotta:
Il valore memorizzato in ind_var è: 0012FF7C
Il valore puntato da ind_var è: a
Il codice ASCII di a è 61
Il programma determina lo stato della memoria illustrato nela figura
seguente
Scarica

Fonda12