Gestione delle aree di memoria /1




Grazie ai puntatori, il concetto di variabile viene generalizzato in quanto grazie ad essi le variabili appartenenti a tutti e quattro i tipi fondamentali possono essere considerate niente altro che delle aree di memoria (e diventano quindi equivalenti tra loro).

A questo punto sembra quasi che la forte tipizzazione, caratteristica del linguaggio C, sparisca quando passiamo dal livello in cui la gestione della memoria viene effettuata mediante le variabili al livello in cui la memoria viene gestita direttamente mediante i puntatori. In realtà, la forte tipizzazione è ancora presente e si traduce nel fatto che è necessario, all'atto della dichiarazione di un puntatore, indicare quale sia il tipo della variabile cui il puntatore fa riferimento.




Qual è il motivo per cui è necessario specificare quale sia il tipo della variabile cui fa riferimento il puntatore ?
Fino ad ora abbiamo sempre detto che un puntatore fa riferimento alla locazione di memoria in cui è contenuta una variabile. A priori, però, non è noto quale tipo di variabile verrà memorizzata in una fissata locazione, perciò non ci sono locazioni per memorizzare interi, locazioni per memorizzare caratteri, o decimali in singola e doppia precisione.

La memoria è in realtà suddivisa in aree aventi tutte la medesima dimensione, nel nostro caso 1 Byte. Per fare un esempio, supponiamo che le celle siano ordinate in questo modo e che per indicare la posizione di una cella basti indicare prima il numero di riga e poi quello di colonna (come a battaglia navale).












  1 2 3 4 5 6 7 8 9
1                  
2                  
3                  
4                  
5                  
6                  
7                  
8                  
9                  














Per individuare la cella colorata di rosso sarà sufficiente dire 37 (ovvero 37 rappresenta l'indirizzo della cella). Ora, in una locazione di un Byte ci sta al massimo un char: come facciamo a memorizzare degli int ? Prendiamo 4 celle adiacenti !
Ad esempio, le 4 celle blu: 82, 83, 84, 85 . Quale dei quattro indirizzi prendiamo come riferimento ?

Prendiamo il primo !
(il più piccolo dei 4)




A questo punto sorge spontanea una domanda: ma se un indirizzo (vale a dire un puntatore) identifica in modo univoco UNA SOLA CELLA DI MEMORIA, come fa il programma a sapere quando fermarsi ?

Lo sa dalla dichiarazione del puntatore: in fase di dichiarazione, infatti, viene specificato il tipo della variabile cui fa riferimento il puntatore.

Se il puntatore fa riferimento ad una variabile di tipo int, il programma sa che, a partire dalla prima cella, può leggere solo le 3 celle adiacenti da 1 Byte ciascuna.

Se il puntatore fa riferimento ad una variabile di tipo double, il programma sa che, a partire dalla prima cella, può leggere solo le altre 7 celle adiacenti da 1 Byte ciascuna.
























// facciamo un esempio

double numero = 4.54 ;
double * punt_d;
punt_d = & numero ;    // ora punt_d vale 52 ( facendo riferimento alla tabella )


 
1
2
3
4
5
6
7
8
9
1
                 
2
                 
3
                 
4
                 
5
                 
6
                 
7
                 
8
                 
9
                 














Passiamo ora ad un esempio di utilizzo dei puntatori. La scorsa volta abbiamo utilizzato una funzione che, ricevendo due puntatori ad intero, scambiava tra loro i valori delle variabili cui i puntatori facevano riferimento. Detto in altri termini, la funzione riceveva l'indirizzo delle aree di memoria in cui erano stati salvati i valori delle variabili, leggeva tali valori e li scambiava tra loro.



Facciamo un esempio analogo, ovvero modifichiamo il valore di una variabile utilizzando una funzione. Prendiamo spunto dalla funzione descritta al punto (b) dell'esercizio 9; la funzione deve inizializzare un char assegnandogli un valore casuale compreso tra due char di riferimento.




Il prototipo della funzione è:

// inizio myfun.h

#include <stdlib.h>

void set_rand_char(char *,char, char);


/* Il primo parametro di tipo puntatore a char serve a passare l'indirizzo della variabile da modificare (ne passo l'indirizzo perché la funzione ne deve alterare il valore). Il secondo ed il terzo parametro rappresentano i due caratteri di riferimento; passo solo il valore perché questi ultimi non devono essere modificati. */


void set_rand_char(char *pchar,char min,char max)
{char differenza = max - min ;
  (*pchar) = min + rand() % (max -min + 1);
  }

// fine myfun.h



Ora utilizziamo la funzione appena implementata.


// inizio esempio

#include "myfun.h"
#define DIM_VECT 50

int main()
{char array[DIM_VECT];
  char *punt;
  char val1 = 'A',val2 = 'z';
  int i = 0;
  srand(132312);   // inizializzo il generatore di numeri casuali

  for(i = 0;i < DIM_VECT ; i++)
    {punt = & (array[i]);
      set_rand_char(punt,val1,val2);}

 return 0;}


// fine esempio





All'interno dell'esercizio i puntatori agli elementi dell'array vengono passati, uno dopo l'altro, alla funzione set_rand_char(). La funzione, in altre parole, riceve una dopo l'altra gli indirizzi delle aree di memoria in cui sono localizzati gli elementi del vettore.




La dichiarazione di un array nel codice corrisponde, all'interno dell'eseguibile ricavato da tale codice, alla creazione di un'area di memoria dalla dimensione pari al prodotto degli elementi dell'array per la dimensione che ciascuno di essi occupa in memoria.

int vet[10];            // corrisponde ad un'area pari a 10*sizeof(int) Byte






IMPORTANTE:

I 40 Byte appena riservati sono contigui all'interno della memoria, ovvero essi non sono sparpagliati in varie zone ma costituiscono un unico blocco.

All'interno di questo blocco di memoria è possibile muoversi usando la notazione a parentesi quadre, inserendo cioè tra le parentesi quadre dell'array l'indice corrispondente all'elemento che ci interessa.


vet[0]
vet[1]
vet[2]
vet[3]
vet[4]
vet[5]
vet[6]
vet[7]
vet[8]
vet[9]

                   















Visto che

  1. sappiamo come passare alle funzioni delle aree di memoria ( -> mediante i loro indirizzi)
  2. sappiamo muoverci all'interno di aree di memoria aventi dimensione superiore a quella di una variabile, come ad esempio gli array (-> usando la notazione a parentesi quadre)
possiamo passare ad una funzione l'intero vettore in un colpo solo ! basta avere a disposizione un indirizzo che ci permetta di specificare come raggiungere l'array.





















In questo ci viene in aiuto una caratteristica del linguaggio:

Nel dichiarare un array di elementi, il nome dell'array diventa automaticamente un puntatore al primo elemento del vettore.


int elenco[40];        // (#) v. sotto

elenco è una variabile di tipo puntatore ad intero, cioè di tipo int *, che contiene l'indirizzo del primo elemento del vettore



in altre parole



il valore di elenco è pari a & ( elenco[0])




ATTENZIONE: la variabile elenco è da considerarsi una costante.
Se l'array è stato dichiarato usando la modalità indicata sopra (#), MAI modificare il valore della variabile avente come identificatore il nome dell'array.













Altra regola da tenere in mente:


Dopo aver dichiarato un array ci muoviamo all'interno della corrispondente area di memoria usando il nome dell'array (ovvero il puntatore al primo elemento) e le parentesi quadre; tra le parentesi quadre inseriamo l'indice dell'elemento di cui ci interessa leggere e/o modificare il valore.














Se quindi la funzione riesce ad evere le sequenti informazioni:

(1) valore del puntatore al primo elemento dell'array ( cioè il puntatore al primo elemento dell'array )

(2) indice dell'elemento di cui ci interessa leggere/modificare il valore


anche essa sarà in grado di modificare tutti gli elementi dell'array.




































// inizio esempio

#include <stdlib.h>
#define DIM_VECT 50

void set_rand_array(char *,char, char);


void set_rand_array ( char *pchar , char min , char max )
{char differenza = max - min ;
  int i; // dichiaro qui il contatore

  for( i = 0 ; i < DIM_VECT ; i ++ )
   { pchar[i] = min + rand() % (max -min + 1); }

}


int main()
{char array[DIM_VECT];

  char val1 = 'A', val2 = 'z';
  int i = 0;
  srand(132312);   // inizializzo il generatore di numeri casuali

  set_rand_array ( array , val1 , val2 ) ;

   return 0;}


// fine esempio




Se non vogliamo utilizzare una macro per definire la lunghezza del vettore, dobbiamo aggiungere un parametro alla funzione in modo da farle sapere quale sarà il massimo indice utilizzabile per gestire gli elementi del vettore.


// inizio myfun.h

#include <stdlib.h>

void set_rand_array(char *, char, char, int);

void set_rand_array ( char *pchar , char min , char max, int dimensione)
{char differenza = max - min ;
  int i;

  for( i = 0 ; i < dimensione ; i ++ )
   { pchar[i] = min + rand() % (max -min + 1); }

}

// fine myfun.h




// inizio codice
#include "myfun.h"
#define DIM_VECT 50


int main()
{char array[DIM_VECT];

  char val1 = 'A', val2 = 'z';
  int i = 0;
  srand(132312);   // inizializzo il generatore di numeri casuali

  set_rand_array ( array , val1 , val2, DIM_VECT) ;

   return 0;}


// fine codice



Ora che abbiamo una funzione che inizializza l'array, possiamo costruire una funzione che lo ordina !
Per ordinare l'array, ad esempio in ordine crescente, mi basta sapere di che tipo sono gli elementi e quanti sono.

Implementiamo una funzione che dispone in ordine crescente gli elementi di un array.








// inizio myfun.h


void ordina_cresc(int *, int ) ;



void ordina_cresc(int *vettore, int lunghezza)
{int i = 0, temp = 0;
  int scambia = 1 ;

while(scambia)
 {scambia = 0;

  for( i = 0 ; i < lunghezza ; i ++)
    {if(vettore[i] < vettore[i+1])
       {;}
      else
       {temp = vettore[i];
        vettore[i] = vettore[i+1];
        vettore[i+1] = temp;
        scambia = 1; }
    }

 }

}


// fine myfun.h







// inizio codice

#include "myfun.h"
#include <stdio.h>
#define DIM 60


int main()
{int array[DIM];

 printf(" richiamo la funzione che inizializza il vettore ");

 set_rand_int(array,DIM);   // provate ad implementare questa funzione

 printf(" richiamo la funzione che ordina gli elementi del vettore ");

 ordina_cresc(array,DIM);

 return 0;}






// fine codice