Gestione delle aree di memoria /2 Nei programmi scritti fino ad ora le aree di memoria utilizzate dalle variabili erano state sempre riservate utilizzando la dichiarazione delle variabili medesime. Ovvero, quando ci sono serviti 4 Byte per memorizzarci un valore di tipo intero, noi li abbiamo richiesti dichiarando una variabile di tipo int: int numero; // riserva 4 Byte per il programma ![]() Come esemplificato nel disegno, fino ad ora le funzioni ci sono servite per modificare i valori delle variabili, ovvero le funzioni ricevevano un puntatore (ad una singola variabile o ad un intero array) e mediante tale puntatore accedevano alle aree di memoria in cui le variabili erano state memorizzate, modificandone eventualmente il valore. Supponiamo ora di voler effettuare il compito opposto: vogliamo che la funzione riservi l'area di memoria di cui abbiamo bisogno, che la inizializzi e che restituisca poi il puntatore a tale area. ![]() Implementiamo brevemente una funzione che svolga un compito simile:
Qual è il problema connesso con un'implementazione di questo tipo ? La variabile numero è una variabile locale e quindi la durata di tale variabile e della corrispondente area di memoria è soggetta alle regole che il linguaggio C impone alle variabili locali. E' possibile slegare la durata della variabile con cui gestiamo l'area di memoria dalla durata dell'area di memoria stessa ? La libreria standard del linguaggio C mette a disposizione delle funzioni per permettere al programmatore di riservare della memoria senza utilizzare la metodologia indicata sopra, ovvero svincolando la durata dell'area di memoria da quella della variabile a cui essa, fino ad ora, era stata collegata. Tali funzioni vengono dichiarate in stdlib.h ed i loro prototipi sono:
Nel caso in cui un'area di memoria sia stata riservata con malloc() o con calloc() essa rimane accessibile fino a quando non viene liberata con free(). La durata dell'area di memoria , quindi, viene stabilita direttamente dal programmatore, non dalle consuete regole che determinano la durata delle variabili. NOTA: (1) l'argomento della funzione malloc() è un intero (che rappresenta la dimensione dell'area di memoria da allocare) e quindi NON ha alcuna correlazione con ciò che poi sarà memorizzato all'interno dell'area (questo ribadisce ancora una volta la generalità del concetto di puntatore). (2) Le funzioni malloc() e calloc() sono di tipo void*, ad indicare che l'area di memoria può essere gestita con un puntatore di un qualsiasi tipo (a conferma ulteriore della generalità del concetto di puntatore). // inizio esempio #include <stdio.h> #include <stdlib.h> int main() {double * var; // dichiaro un puntatore a double var = (double *) malloc(sizeof(double)); // riservo un'area di memoria per un double if(var == NULL) {printf("\n allocazione fallita !"); exit(0);} printf("inserisci un valore decimale"); scanf("%lf",var); printf("il doppio del valore inserito e\' : %f", 2 * (*var )); free(var); // libero l'area di memoria precedentemente allocata return 0; } // fine esempio Ora siamo in grado di creare un'area di memoria che non è soggetta alle regole di durata delle variabili locali. Sfruttiamo questa possibilità per implementare una funzione che gestisce l'input di double da parte dell'utente // inizio esempio #include <math.h> // ![]() #include <stdio.h> #include <stdlib.h> double *operazioni(void); double * operazioni (void) {double *punt; // questa è una variabile locale ! /* il valore della variabile locale è l'indirizzo di un'area di memoria; la durata dell'area di memoria è superiore a quella della durata della variabile che contiene l'indirizzo di tale area */ punt = (double *) malloc( 1 * sizeof(double)); if( punt == NULL) {printf("\n allocazione non riuscita"); return punt; } /* interrompo l'esecuzione della funzione prima che essa usi quell'area di memoria e commetta un accesso illegale alla memoria */ do { printf("\n inserisci un numero decimale positivo"); scanf("%lf",punt); } while(*punt > 0.) printf("\n il valore inserito vale: %f",*punt); printf("\neseguo ora alcune operazioni :"); printf("\nil logaritmo del numero vale %f",log(*punt)); printf("\nla radice quadrata del numero vale %f",sqrt(*punt)); printf("\nil valore di punt e\' pari a : %p",punt); return punt; /* a questo punto la durata della variabile puntatore termina; la funzione, però, restituisce il valore della variabile, valore che rappresenta l'indirizzo dell'area di memoria allocata con malloc(). Tale area rimane riservata fino a quando non la liberiamo con la funzione free() */ } int main() {double *variabile; variabile = operazioni(); if(variabile == NULL) exit(0); printf("\n visualizzo, da main(), il valore della variabile e del corrispondente puntatore"); printf("\n variabile: %f indirizzo : %p",*variabile,variabile); printf("\n ora libero l'area di memoria allocata con malloc()"); free(variabile); return 0;} // fine esempio Rifacendo un ragionamento simile a quello effettuato ieri, visto che ora siamo in grado di restituire un puntatore ad un'area di memoria, possiamo restituire l'indirizzo di un'area di dimensione arbitraria (ad esempio un array). L'allocazione dell'area di memoria può essere fatta utilizzando la funzione malloc() int *vector; vector = (int *) malloc(50 * sizeof(int)); if( vector == NULL ) {printf("allocazione non riuscita"); exit(0);} Per muoverci all'interno dell'area allocata in modo dinamico (mediante malloc() o calloc()) possiamo usare l'aritmentica dei puntatori (ricordando che vector, per come lo abbiamo inizializzato, rappresenta il puntatore al primo elemento contenuto nell'area) vector // puntatore al primo elemento vector + 3 // puntatore al quarto elemento *(vector + 3) = 4; // assegno al quarto elemento il valore 4 Come ci si muoveva all'interno degli array allocati in modo statico ? int vettore[40]; vettore[6] = 4; // inizializzo il settimo elemento Ieri abbiamo visto che la variabile avente un nome pari a quello dell'array non è altro che un puntatore che contiene l'indirizzo del primo elemento dell'array. Stando così le cose, per accedere ad un array si può accedere sia usando la notazione a parentesi quadre sia l'arimetica dei puntatori, l'importante è conoscere il valore del puntatore al primo elemento dell'array.
Implementiamo ora una funzione che riceve tre parametri di tipo intero e che restituisce un valore di tipo int *. Il prototipo della funzione è: int * build_array(int dim,int min,int max); La funzione deve allocare un'area di memoria tale da contenere un numero di variabili pari a dim [scelgo quindi la dimensione dell'array IN FASE DI ESECUZIONE]; le variabili devono essere inizializzate con dei numeri casuali compresi tra min e max. Tali valori devono poi essere disposti in ordine crescente.
Esercizio: Implementiamo una funzione che non accetta parametri e che restituisce un puntatore a char. La funzione deve svolgere questo compito: deve chiedere all'utente di inserire un numero molto grande e poi deve estrarre da quel numero i 4 char di cui esso è composto (v. qui per informazioni). Tali 4 char devono essere inseriti in un array di 5 char: i primi quattro elementi devono essere pari ai 4 valori estratti dall'intero mentre il quinto carattere deve essere pari a '\0' (carattere con codice ASCII nullo) Seguire una delle due tracce (o entrambe) per determinare i valori dei 4 char: traccia 1 (xamanti degli operatori bit-a-bit): per visualizzare un dato gruppo di 8 bit che sta all'interno dei 32 bit dell'intero usare gli operatori di shift in questo modo: spostare verso destra la rappresentazione binaria dell'intero fino a quando gli 8 bit che ci interessano stanno in fondo a destra. A questo punto assegnare il valore così ottenuto ad una variabile di tipo char (l'assegnazione, grazie al cast implicito, elimina automaticamente eventuali bit ancora presenti a sinistra degli 8 che ci interessano) traccia 2 (xamanti dei puntatori) senza usare shift, divisioni per due o l'operatore modulo accedere direttamente al gruppo di 8 bit corrispondente al carattere di cui ci interessa memorizzare il valore. Fate una prova con 1918986339 |