====== Esercitazione allocazione dinamica ====== ===== Esercizio 1: Sequenze e VLA ===== Realizzare un programma C che legge e memorizza in un VLA una sequenza di double di lunghezza non nota a tempo di compilazione (ma nota all'utente a tempo di esecuzione). Richiedere all'utente di specificare la lunghezza prima di immettere la sequenza. ===== Esercizio 2: Sequenze e malloc() ===== Modificare la soluzione all'esercizio 1 in modo da utilizzare la funzione ''malloc()'' per allocare l'array dopo aver letto la lunghezza. Verificare la corretta allocazione e gestire gli errori. ===== Esercizio 3: Allocazioni in grossi blocchi ===== Eseguire il seguente programma C #include #include #define SIZE 10000000000 int main (void) { double * h; h = malloc(SIZE*sizeof(double)); if ( h == NULL ) { perror("malloc"); exit(EXIT_FAILURE); } printf("Allocato h = %p\n",(void *) h); return 0; } cosa succede ? Cercate di capire che cosa succede quando va in esecuzione ''perror()'' andando a verificare il manuale (sezione 3 ''man 3 perror'') ===== Esercizio 4: realloc() ===== Modificare la soluzione all'esercizio 2 in modo da utilizzare la funzione ''relloc()'' per fare crescere dinamicamente l'array senza richiedere la lunghezza della sequenza. Verificare la corretta allocazione e gestire gli errori. ===== Esercizio 5: Effetti collaterali indesiderati ===== Se ho una variabile intera "int i", posso modificarne il valore con una funzione che riceve un puntatore "&i" (di tipo ''int *'' ) alla cella di memoria di "i". Allo stesso modo, se ho un array di interi '' int * a'', posso modificarne il valore, ovvero creare un nuovo array a cui ''a'' e' un riferimento con una funzione che riceve un puntatore ''&a'' (di tipo ''int * * '') alla cella di memoria di a. Le due funzioni ''array1()'' e ''array2()'' qui sotto tentano di fare proprio questo. Tuttavia, dopo aver inizializzato l'array B notiamo che il contenuto dell'array A e' cambiato. Qual'e' il motivo di questo comportamento? (//suggerimento//: guardare le slides su VLA) #include // questa funzione riceve un puntatore ad un array di interi, ed ha il compito di creare un array in qu void array1(int ** c); // questa funzione si comporta come array1 ma crea un array differente void array2(int ** c); int main(){ int * aA; // array A int * aB; // array B array1(&aA); printf("array A: %d, %d, %d \n",aA[0],aA[1],aA[2]); array2(&aB); printf("array B: %d, %d, %d \n",aB[0],aB[1],aB[2]); // ma nel frattempo, cosa e' successo all'array A? printf("array A: %d, %d, %d \n",aA[0],aA[1],aA[2]); return 0; } void array1(int ** c){ int n = 3; // avendo usato a[n], la dimensione non e' nota a tempo di compilazione // quindi non possiamo utilizzare la costruzione con valore letterale "a[n] = {1,2,3}" int a[n]; a[0] = 1; a[1] = 2; a[2] = 3; *c = a; } void array2(int ** c){ int n = 3; int a[n]; a[0] = 4; a[1] = 5; a[2] = 6; *c = a; } Correggere il codice in modo che le funzioni array1() e array2() allochino i rispettivi array sullo heap con la funzione "malloc()", e venga quindi evitato l'effetto collaterale di B su A. ===== Esercizio 6: Funzione di allocazione/deallocazione di matrici ===== Le matrici possono essere rappresentate come array di puntatori a righe. Si richiede di implementare le funzioni per la creazione e la deallocazione di matrici ''nxm'' di double. In particolare, la funzione /** crea una matrice di double rappresentata come array di puntatori a righe \param n numero di righe \param m numero di colonne \retval NULL se si e' verificato un errore \retval p puntatore alla matrice allocata altrimenti */ double ** mat_new (unsigned n, unsigned m); crea una matrice con n righe ed m colonne e ne restituisce il puntatore. E /** libera la memoria occupata da una matrice di double rappresentata come array di puntatori a righe \param a puntatore alla matrice da deallocare \param n numero di righe */ void mat_free (double** a, unsigned n); Sviluppare un opportuno main che testa che l'allocazione e la deallocazione siano stata effettuata correttamente, anche in caso di errori in corso d'opera.