sabato 2 febbraio 2013

[GUIDA] Allocare dinamicamente array e matrici in C

Una piccola introduzione sul perché utilizzare l'allocazione dinamica e come questa funzione. Vediamo poi  come allocare dinamicamente un array e una matrice in C. Infine vi fornirò un paio di funzioni utili da riutilizzare per i vostri scopi.






Come funziona

Innanzitutto, perché l'allocazione dinamica? Quando andiamo a definire una struttura in modo statico, che può essere un array o una matrice o altro, dobbiamo conoscere a priori la sua dimensione. Questo perché la sua allocazione viene fatta all'avvio del programma e non cambia durante la sua esecuzione. Vi ricordo che la memoria di un programma è strutturata nel seguente modo:




La nostra struttra allocata in modo statico si troverà pertanto in segment (la parte composta da "dati inizializzati" e non). Tuttavia non sempre si può sapere a priori la dimensione della struttura da dover allocare. Pensate ad esempio un programma che richiede di memorizzare un array di tot elementi, dove questo tot è dato in input dall'utente. Come faccio a sapere quanto metterà l'utente? Certo, voi mi direte che si può prendere a priori una dimensione molto grande e allocare quella. Però poi l'utente sarà obbligato a rispettare quella dimensione. Per non parlare dell'eventuale spazio sprecato nel caso si allocasse ad esempio un array di 1000 elementi e se ne utilizzassero solo 5. 
Il modo migliore per affrontare questo problema consiste appunto nell'allocazione dinamica. Essa consiste nell'allocare delle strutture dati durante l'esecuzione del programma, quindi non devo sapere a priori la loro dimesione. Per queste strutture è riservata l'area heap, in figura. Questa cresce dinamicamente verso l'alto a seconda di quanto la utilizzeremo.
NOTA: una volta che la memoria allocata non è più utilizzata, è sempre buona norma deallocarla. Si potrebbe altrimenti andare incontro ad un "memory leak", ovvero se ad esempio richiamassimo di frequente una funzione che si alloca per sè della memoria e non la rilascia dopo averla utilizzata, è facile che questo porti all'esaurimento della memoria utilizzabile dal programma, che di conseguenza finirà in crash.

Come avviene

Ci vengono fornite già pronte 4 utilissime funzioni:
  • void *calloc(size_t size)
Alloca size byte nello heap. La memoria viene inizializzata a 0.
La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e NULL in caso di fallimento, nel qual caso errno assumerà il valore ENOMEM.
  • void *malloc(size_t size)
Alloca size byte nello heap. La memoria non viene inizializzata.
La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e NULL in caso di fallimento, nel qual caso errno assumerà il valore ENOMEM.
  • void *realloc(void *ptr, size_t size)
Cambia la dimensione del blocco allocato all'indirizzo ptr portandola a size.
La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e NULL in caso di fallimento, nel qual caso errno assumerà il valore ENOMEM.
  • void free(void *ptr)
Dealloca lo spazio di memoria puntato da ptr. La funzione non ritorna nulla e non riporta errori.

Array

Supponiamo di voler allocare un array di interi di dimensione N, non nota a priori. La sintassi è la seguente:

// dichiaro la variabile puntatore int * array;
// alloco la memoria dinamicamente
array = (int*)malloc(N * sizeof(int));
// controllo se tutto è andato bene
if (NULL == array) { /* gestisco l'errore... */ }

/* Eeguo le mie operazioni...*/

// infine dealloco la memoria
 free(array);
 // mi assicuro che il puntatore non sia più allocato
 array = NULL;


La variabile array conterrà quindi un puntatore alla memoria allocata, a cui potremo tranquillamente accedere con array[i].
Cosa abbiamo fatto di preciso? Abbiamo detto: data la dimensione di un intero (vogliamo allocare array di int), allocami un'area di N*la dimensione, che è appunto il totale che vogliamo. 

Matrici

Una matrice non è altro che un array di array. Per prima cosa quindi si allocherà un array la cui dimensione sarà pari al numero delle colonne, poi per ogni colonna, alloco un array che avrà tanti elementi quante sono le righe. Un esempio per una matrice di interi di dimensione NxM:

// creaiamo un array di M elementi  
matix = (int**)malloc(M * sizeof(int*));

 // ora, per ogni elemento allochiamo un array di grandezza N  
for (i = 0; i < M; i++) {    
   matrix[i] = (int*)malloc(N * sizeof(int));  
}

Per accedere ad una locazione vi basterà fare: matrix[M][N].
Per deallocare la memoria vi basterà utilizzare questa pratica funzioncina:
void free_matrix(int **array, int ncols) {
   int i;
    for (i=0; i<ncols; i++)    
      free((void*)array[i]);  
    free((void*)array);  
    array=NULL;
}

array sarà la nostra matrice, ncols è il numero delle colonne, quindi M.

Qualche funzione utile

Eccovi infine alcune semplici funzioni per agire sulle vostre strutture dinamiche:

Stampa un array di dimensione size:
void print_array(int *array, int size) {  
    int j;
    for(j=0;j<size;j++)      
      printf("%d ",array[j]);
 }

Stampa una matrice (mat) di dimensioni NxM
void print_matrix(int **mat, int n, int m) {  
    int i, j;
    for (i = 0; i < n; i++) {      
       for (j = 0; j < m; j++) {
             printf("%d",mat[j][i]);      
       }      
       printf("\n");  
    }
}

Copia la matrice b dentro alla matrice a. Devono avere stessa dimensione.
void copyMatrix(int **a,int **b,int row,int col) {
  int i,j;
  for(i=0;i<row;i++){
         for(j=0;j<col;j++){
             a[j][i]=b[j][i];      
        }  
  }
}

Nessun commento:

Posta un commento