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 = № 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 = № 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 = № 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