PUNTATORI E ARRAY.

Nel capitolo 6 sono stati introdotti gli array, ora che
abbiamo una certa familiarita' con i puntatori possiamo
approfondire l'argomento.

Dopo aver definito un array, ad esempio di caratteri, il
compilatore riserva una sezione contigua della memoria
sufficiente a contenere tutti gli elementi dell'array stesso.
Ovviamente ogni elemento dell'array potra' essere identificato
dall'indirizzo di memoria nel quale e' stato inserito.
Vediamo un semplice programma che ci aiutera' a chiarire il
concetto.

#include 

int main()
{
   int i=0;
   char parola[]="Sinclair";

   while( i < 8 ) {
     printf("indirizzo dell'elemento %d di parola e': %ld\n",
             i, &parola[i] );
     printf("ed il contenuto e': %c\n", parola[i] );
     ++i;
   }
}

che dara' un output tipo:

indirizzo dell'elemento 0 di parola e': 4364159
ed il contenuto e': S
indirizzo dell'elemento 1 di parola e': 4364160
ed il contenuto e': i
indirizzo dell'elemento 2 di parola e': 4364161
ed il contenuto e': n
indirizzo dell'elemento 3 di parola e': 4364162
ed il contenuto e': c
indirizzo dell'elemento 4 di parola e': 4364163
ed il contenuto e': l
indirizzo dell'elemento 5 di parola e': 4364164
ed il contenuto e': a
indirizzo dell'elemento 6 di parola e': 4364165
ed il contenuto e': i
indirizzo dell'elemento 7 di parola e': 4364166
ed il contenuto e': r

Un altro modo per risalire al valore di ogni elemento
dell'array puo' consistere nel modificare la seconda printf
nella seguente:

(B)
    printf("ed il contenuto e': %c\n", *(&parola[i]) );

oppure

(C)
    printf("ed il contenuto e': %c\n", *(parola+i) );

Queste due scritture svolgono la stessa funzione e sono,
pertanto, intercambiabili.

Esaminandole piu' nel dettaglio possiamo scoprire qualcosa di
piu' sui puntatori.

(A)
   printf("ed il contenuto e': %c\n", parola[i] );

si accede direttamente all'elemento i-esimo dell'array.

(B)
   printf("ed il contenuto e': %c\n", *(&parola[i]) );

Utilizzare l'operatore di indirezione su di un indirizzo
equivale ad accedere al contenuto della locazione di memoria
che, in questo caso, e' parola[i].  L'utilizzo di puntatori ad
indirizzi e', anche se corretto, non particolarmente razionale
ed ha qui solo carattere dimostrativo.

(C)
   printf("ed il contenuto e': %c\n", *(parola+i) );

questa scrittura e' invece assai piu' interessante in quanto
permette di introdurre una interessante peculiarita' del C:
l'aritmetica dei puntatori.


-- ARITMETICA DEI PUNTATORI --

Poiche' un puntatore e' un numero, come tale puo' essere
trattato: potremo addizionarlo o sottrarlo e, dal momento che
un'array e' memorizzato in locazioni contigue di memoria,
sara' logico supporre che incrementando o decrementando il suo
valore sia possibile spostarsi da una cella di memoria ad
un'altra.

Analizziamo il nostro caso:

   indirizzo dell'elemento 0 di parola e': 4364159
   ed il contenuto e': S

Quando i vale 0 *(parola+i) equivale a parola[0], ovvero
l'indirizzo 4364159 contiene il valore "S". Incrementando i di
un'unita' *(parola+i) equivarra' a parola[1] ed il suo
contenuto sara' "i" e cosi' via fino al termine dell'array.

NOTARE la scrittura *(variabile+incremento): questo modo di
digitare l'istruzione indica al compilatore di valutare
l'indirizzo della variabile ("parola" nel nostro caso),
spostarsi di "incremento" bytes rispetto al suddetto indiriz-
zo e, infine, restituirne il contenuto.  Scrivere:
*variabile+incremento potrebbe dare luogo a problemi di
interpretazione in fase di parsing percio' e' buona norma
utilizzare le parentesi, il che favorisce anche una migliore
comprensione da parte del programmatore.

Dal momento che il nostro array e di tipo char, ogni
incremento di unita' del suo indice spostera' il valore della
memoria di un byte.  Se avessimo utilizzato un array di tipo
short ogni incremento ci avrebbe fatto spostare di due byte;
quattro byte sarebbero stati utilizzati per un array di tipo
int, ecc...  Pertanto occorre ricordare che quando si
utilizzano operazioni aritmetiche sui puntatori i risultati
delle stesse influiranno sulla memoria in maniera
proporzionale alle dimensioni del tipo utilizzato!
Naturalmente gli incrementi ed i decrementi potranno essere di
valore superiore ad uno.  L'unica cosa da verificare e' la
dimensione dell'array: indirizzare valori inferiori alla
locazione di inizio dello stesso o superiori alla sua
dimensione massima causera' malfunzionamenti del programma in
quanto si potranno alterare altri dati o aree di memoria
riservate al sistema operativo.

Un esempio di utilizzo dei puntatori puo' essere la scrittura
di un programma che scrive una stringa di caratteri al
contrario.  Questa, oltre che ad essere un ottimo sistema per
dimostrare l'utilizzo dei puntatori e' anche una routine che
puo' essere utilmente impiegata in campo grafico per creare
l'effetto "rifletti" di un'immagine.

/*
  RIFLESSIONE.C
*/

#include 

void main( void );

void main( void )
{
    char stringa[]={"Sinclair QL"};
    char *s1, *s2;

    s1 = s2 = stringa;
    /* s1 e s2 puntano a stringa */

    while( *s2 ) s2++;
    while( --s2 >= s1 )
       putchar( *s2 );
    printf( "\n" );
}


Analizzando il programma notiamo che:

1- si dichiara un array denominato stringa e gli si assegna
   un valore;
2- si dichiarano due tipi puntatori s1 ed s2;
3- si fa si' che s1 ed s2 puntino all'indirizzo di stringa;
4- con il primo while si incrementa l'indirizzo di s2 fino a
   quando si raggiunge il terminatore di stringa, ovvero fino
   a quando *s2 e' diverso da 0 (ricordate che in C un test
   logico e' vero fino a che e' diverso da 0? );
5- al termine del primo while si e' raggiunto l'indirizzo di
   fine stringa;
6- con il secondo while si decrementa l'indirizzo contenuto
   in s2 fino a quando si raggiunge il valore dell'indirizzo
   contenuto in s1 (ovvero l'inizio di stringa) e si stampa
   il contenuto della locazione di memoria puntata da *s2;
7- al termine avremo stampato il contenuto della nostra
   variabile partendo dall'ultima lettera fino ad arrivare
   alla prima.

Analogo discorso vale con array di tipi diversi dal char, per
cui potete provare a scrivere la stessa routine con un array
di interi, avendo cura di definire i puntatori di tipo int
invece che char e, magari, utilizzare una printf al posto
della putchar.

Esistono ancora altri utilizzi dei puntatori: parametri di
funzioni e puntatori a funzione.  Tuttavia, dal momento che
non abbiamo ancora discusso l'argomento funzioni, si rimanda
il discorso al momento opportuno.

Nel prossimo paragrafo verra' trattata l'allocazione dinamica
della memoria e si vedra' come i puntatori siano molto
importanti in questo caso.

Roberto Porro