






Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Appunti chiari e sintetici su allocazione dinamica della memoria e strutture dati dinamiche (liste), con esempi guidati. Argomenti: allocazione dinamica (malloc, free), array dinamici, liste e relativi sottoprogrammi (inserimento in testa, in coda...)
Tipologia: Appunti
1 / 12
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!







Il C permette di assegnare la giusta quantità di memoria (solo e solamente quella necessaria) alle variabili del programma. In particolare l’uso della memoria allocata dinamicamente risulta utile con gli array. Utilizzare queste caratteristiche del C permette di creare programmi altamente portabili, in quanto utilizzano di volta in volta i valori giusti per la piattaforma. Una piccola nota su come è organizzata la memoria è doverosa: la memoria è divisa sostanzialmente in due parti, una statica , che contiene tutto quello che sappiamo verrà allocato (come una variabile int ) e che si chiama Stack , ed una dinamica , cioè in cui la dimensione di memoria per rappresentare qualche elemento del programma può cambiare durante l’esecuzione del programma, che si chiama Heap. Per far funzionare il programma dobbiamo includere la libreria <stdlib.h> , senza la quale queste funzioni non potrebbero fare il loro dovere. Presentiamo un esempio per chiarire meglio l’uso delle funzioni principali, ovvero: malloc , free e sizeof (verranno spiegate bene nel prossimo paragrafo) In particolare allocheremo la memoria per un array con malloc(), lavoreremo sull’array stesso ed, infine, libereremo la memoria con free(). In questo programma possiamo notare l’uso della funzione malloc() che ritorna un puntatore generico, corrispondente al punto di inizio, in memoria, della porzione riservata della dimensione “intera” passata come argomento; se la memoria richiesta non può essere allocata, ritorna un puntatore nullo. Nel caso citato si può notare che è stato usata la funzione sizeof per specificare il numero esatto di byte, mentre è stato effettuato un cast per convertire il tipo di dato “ puntatore generico ” a “ puntatore ad int ”, questo per garantire che i puntatori aritmetici vengano rappresentati correttamente.
giovedì 12 dicembre 2013 10:
Introduciamo l'argomento con un altro esempio: Abbiamo un file con dei valori, di lunghezza indefinita, e vogliamo trovare in un nuovo file i valori ordinati. Supponiamo che nel file ci stiano al più 100 interi. Chiaramente mi serve della memoria per ordinare i dati, posso utilizzare un array in questo caso di dimensione prefissata. Vediamo come implementare il main che utilizza i sottoprogrammi (si suppone già definiti) ordinaArray, stampaArray, riempiArray: Come faccio però a creare un programma che vada bene con un numero di interi non prefissato? Mi serve uno strumento che all'inizio dell'elaborazione mi dica in modo chiaro quanti dati mi serve memorizzare. Bisogna poter chiedere una quantità di memoria , usarla, e restituirla quando non serve più. Non si parlerà più di un array statico, perché è appunto di dimensione prefissata. Ecco quindi la necessità di introdurre l'allocazione dinamica della memoria. Vantaggi : poter utilizzare una quantità definita di memoria senza dichiarare un array statico di dimensioni maggiori, lasciando dello spazio in memoria non utilizzato. Vediamo come implementare questo strumento nel nostro programma: Nella dichiarazione bisognerà sostituire int dati[NUM],dim con: int dim; int *dati (Si tratta di un puntatore)
sabato 14 dicembre 2013 20:
Come già visto, le due funzioni principali sono malloc e free. La chiamata di funzione: malloc (sizeof (TipoDato) ); crea in memoria una variabile di tipo TipoDato e restituisce come risultato l 'indirizzo della variabile creata (più precisamente, il valore restituito è l 'indirizzo del primo byte riservato alla variabile ). Se p è una variabile di tipo puntatore a TipoDato, l'istruzione: p = malloc (sizeof (TipoDato) ); assegna l'indirizzo restituito dalla funzione malloc a p. Di conseguenza, dopo l'esecuzione di questa istruzione p punta alla nuova variabile perdendo il suo valore originario. N.B. La memoria viene allocata per un dato di tipo TipoDato, non per p, che invece è una variabile già esistente. In C una variabile creata dinamicamente è necessariamente anonima : a essa si può fare riferimento solo tramite puntatore; una variabile dichiarata mediante un proprio identificatore può invece essere indicata tramite l'identificatore stesso ma può anche essere puntata. Un puntatore si comporta come una normale variabile identificata (esso può essere indicato mediante identificatore o puntato da un altro puntatore). Simmetricamente, la chiamata di funzione free(p) rilascia lo spazio di memoria puntato da p; ciò significa che la corrispondente memoria fisica è resa nuovamente disponibile per qualsiasi altro uso. È importante notare che la funzione free deve ricevere, come parametro in ingresso, un puntatore al quale era stato assegnato come valore l'indirizzo restituito da una funzione di allocazione dinamica di memoria (malloc, nel nostro caso). L'uso delle funzioni malloc e free richiede l'inclusione delle librerie <stlib.h> tramite l'istruzione #include <stdlib.h> Esempio : Scrivere un sottoprogramma che ricevuta in ingresso una stringa ne crea una nuova della dimensione strettamente necessaria per ospitare il contenuto della stringa ricevuta in ingresso e la restituisce al chiamante. N.B. Il sottoprogramma stringdup è incluso nelle librerie <string.h> perciò non è necessario dichiararlo o definirlo per poterlo utilizzare nel main; in questo esempio abbiamo visto come è strutturato, ma nel prossimo esempio si può notare come esso venga utilizzato normalmente.
martedì 17 dicembre 2013 18:
Se volessi un programma che, sfruttando il sottoprogramma stringdup, mi permetta di allocare della memoria per dei cognomi: Liste Le liste sono uno degli argomenti più delicati da trattare, per il semplice fatto che sono uno strumento potente e versatile, ma anche molto fragile. Basti pensare che l’uso delle liste permette di impostare programmi di ordinamento in modo molto efficiente, ed offre quella dinamicità, tra l’altro tipica del C, di cui si può avere bisogno durante lo sviluppo di un programma. Una lista non è altro che una collezione di elementi omogenei, ma, a differenza dell’array , occupa in memoria una posizione qualsiasi, che tra l’altro può cambiare dinamicamente durante l’utilizzo della lista stessa; inoltre la sua dimensione non è nota a priori e può variare nel tempo (l’opposto dell’array, in cui la dimensione è ben nota e non è modificabile). Una lista può contenere uno o più campi contenenti informazioni, e, necessariamente, deve contenere un puntatore per mezzo del quale è legato all’elemento successivo. Lo spazio che viene richiesto non è solo lo spazio necessario a memorizzare il dato del tipo TipoDato, ma anche un'altra informazione che serve a puntare all'elemento successivo, quindi verrà memorizzato anche un indirizzo. Le liste sono indispensabili quando si vuole trattare una quantità di dati non stimata, non dimensionabile, e che può essere incrementata nel tempo. A causa dell'uso dei puntatori, l'accesso alla lista avviene proprio attraverso il puntatore al suo primo elemento. Nel main dovrò dichiarare quindi solo un puntatore al primo elemento della lista. Chiaramente dovrò sapere quante liste mi servono. L'indirizzo del primo elemento viene chiamato testa della lista. All'inizio di ogni programma, in fase di dichiarazione, la testa della lista sarà vuota (punta a NULL). Questo è vero per qualsiasi programma. Significa quindi che in qualsiasi lista con numero di elementi diverso da 0, la seconda parte dell'ultimo elemento ha valore NULL che assume il significato di fine lista. La lista base ha un solo campo informazione ed un puntatore, come mostrato di seguito: Elemento = informazione + puntatore che, tradotto in codice, risulta essere: N.B. Il campo *next rappresenta un puntatore al tipo strutturato che si sta definendo, perciò non potrò usare come tipo di dato tipoLista_t poiché ancora non è stato dichiarato! mercoledì 18 dicembre 2013 18:
Nel caso di lista vuota, esso non costituisce un caso da gestire a parte, poiché head inizialmente è NULL ma il suo valore poi viene sovrascritto da tmp , perciò il successivo elemento della lista diventa il valore che ho appena allocato. Se voglio fare il sottoprogramma che inserisce un elemento all'inizio della lista , dovrà restituire il nuovo indirizzo di dove inizia la lista: Nel main vediamo la chiamata al sottoprogramma: La rappresentazione grafica dell'inserimento in testa è la seguente: giovedì 19 dicembre 2013 11:
Controllo esistenza di un elemento in una lista Esercizio : sottoprogramma che ricevuti in ingresso una lista e un valore intero n restituisce il numero di volte che n compare nella lista. Innanzitutto mi serve qualcosa per scandire la lista, un indicatore per sapere a quale elemento della lista sto guardando in questo momento. Non si tratta di un ciclo a conteggio, perché in generale non so quanti elementi ho nella lista. Tuttavia si utilizza comunque il ciclo for per la scansione: Inserimento in coda alla lista Si parte sempre dall'inizio e ci fermiamo quando p->next==NULL Eliminare primo elemento della lista Esercizio : sottoprogramma che riceve in ingresso una lista ed elimina il primo elemento della lista: Se la lista fosse vuota, non ci sarebbe nulla da fare, altrimenti bisogna fare in modo che la testa punti al secondo, poi liberare la memoria occupata dal primo: Nel main la chiamata al sottoprogramma sarà: head = eliminaPrimo(head); giovedì 19 dicembre 2013 12:
Procediamo con la definizione del corpo della funzione crea_lista() , la quale crea due puntatori ad ilist_t, uno di nome p e l’altro di nome punt ; queste due variabili serviranno per scorrere la lista, infatti p è il puntatore al primo elemento della lista, mentre punt è un puntatore ausiliario che permette di scorrere la lista; la variabile i è l’indice del ciclo, mentre n serve a memorizzare il numero degli elementi che si intende inserire. Vediamo che è stata usata la funzione malloc() insieme a sizeof per allocare dinamicamente la memoria necessaria ad un elemento della lista; il valore ritornato viene castato ad un puntatore allo spazio allocato, che viene assegnato a p. Assumendo che sia stato inserito un valore di n (il numero di elementi della lista) uguale a 3; nella prima iterazione, viene creato il primo elemento della lista con il codice sopra esposto e viene assegnato (tramite tastiera), ad esempio, il valore 5; Successivamente viene creato un altro puntatore alla prima posizione, di nome “punt”, tale puntatore ausiliario, servirà per scorrere gli elementi della lista e mantenere il collegamento all’ultimo elemento inserito. venerdì 20 dicembre 2013 17:
Proseguendo con l’esecuzione del codice, arriviamo ad inserire il secondo ed il terzo elemento (grazie al ciclo for ): Per prima cosa viene creato un altro oggetto della lista, identificato con punt->pun: poi punt, il puntatore ausiliario, viene fatto puntare, non più al primo elemento, bensì al secondo, all’atto pratico punt diventa il puntatore dell’oggetto da lui puntato (punt = punt->pun;). Quindi viene inserito il campo informazione dell’elemento tramite l’input da tastiera dell’utente; in questo caso viene inserito il valore 20; La procedura, ovviamente, si ripete per il terzo elemento, e, se vi fossero stati altri elementi da aggiungere, si sarebbe ripetuta fino all’inserimento dell’ultimo elemento; venerdì 20 dicembre 2013 17: