Consideriamo il problema di leggere da standard input una sequenza di valori reali terminata da 0.0 creando la lista corrispondente di tipo
typedef struct lista_d { double val; struct lista_d * next; } lista_d_t ;
double leggi_nuovo_valore(void);
dopo aver letto un nuovo valore il programma lo inserisce in testa alla lista. Quando la sequenza e' terminata il programma stampa la lista risultante sullo standard output
Consideriamo le funzioni
lista_d_t * inserisci_testa ( lista_d_t * l, double v); lista_d_t * inserisci_coda ( lista_d_t * l, double v); lista_d_t * inserisci_ord ( lista_d_t * l, double v);
discusse nella lezione sulle liste.
Implementare le seguenti funzioni in modo iterativo e ricorsivo:
/** calcola e restituisce il massimo della lista l */ double max ( lista_d_t * l); /** calcola e restituisce la somma di tutti gli elementi della lista l*/ double somma ( lista_d_t * l); /** libera la memoria occupata dalla lista */ void free_list( lista_d_t * l); /** cancella, se presente, l'elemento che contiene la prima occorrenza del valore v dalla lista l (liberando la memoria) e restituisce la nuova lista */ lista_d_t * cancella_uno ( lista_d_t * l, double v); /** cancella tutti gli elementi di valore del valore v dalla lista l (liberando la memoria) e restituisce la nuova lista */ lista_d_t * cancella_tutti ( lista_d_t * l, double v);
e sviluppare un main() che ne testa il funzionamento.
Realissare le funzioni degli esercizi precedenti utilizzando liste con puntatore al precedente e al successivo
typedef struct elem_d { double val; struct elem_d * prec; struct elem_d * next; } elem_d_t ;
in questo caso la lista puo' essere definita ad esempiocome una struttura con due puntatori, uno alla testa ed uno alla coda
typedef struct lista_d { struct elem_d * head; struct elem_d * tail; } lista_d_t ;
come si modificano gli algoritmi sviluppati precedentemente ?
Vogliamo realizzare degli array di double di grandi dimensioni che contengono solo una piccola percentuale di elementi diversi da 0 come liste concatenate definite come
typedef struct sparse_d { double val; unsigned index; struct elem_d * next; } sparse_d_t ;
nella lista sono presenti solo gli elementi diversi da zero e per ogni elemento e' indicato l'indice a cui corrisponde. La Lista e' mantenuta ordinata rispetto al campo indice.
Realizzare le funzioni put
e get
che permettono di leggere il valore dell'elemento di un certo indice i
(get
) e di modificarne il valore (put
), un possibile prototipo per queste funzioni e':
double put (sparse_d_T * a, unsigned indice); sparse_d_t * put (sparse_d_T * a, double x, unsigned indice);
Con riferimento al main sviluppato per l'esercizio precedente verificare che tutta la memoria allocata venga deallocata prima dell'uscita dal main().
Per la verifica si utilizzi la funzione mtrace
e l'utility mtrace
, questi strumenti tracciano le azioni di allocazione e deallocazione di memoria compiute dal programma per verificare la presenza di memory leak cioe' memoria non deallocata.
Per fare questo procedere come segue:
man 3 mtrace
mcheck.h
-g
per includere le informazioni di debugging. Ad esempio se il mio file si chiama main.c
posso compilare conbash$ gcc -Wall -pedantic -g -o prova main.c
MALLOC_TRACE
al path del file in cui vogliamo che la mtrace()
registri le informazioni sugli accessi di memoria. Ad esempio se voglio registrare le informazioni nel file ./mtrace.out
devo usare il comandobash$ export MALLOC_TRACE=./mtrace.out
bash$ ./prova
./mtrace.out
sono registrati gli accessi in formato testuale non facilmente comprensibile. Interpretarlo con l'utility mtrace. Ad esempio sempre riferendosi al nostro esempio invocarebash$ mtrace ./prova ./mtrace.out
questo rispondera' No memory leaks
se tutta la memoria e' stata deallocata o fornira' indicazioni su dove e' stata allocata la mamoria rimasta da deallocare.
Verificare la correttezza degli accessi ai puntatori dello heap compiuti dalle funzioni su liste sviluppate negli esercizi precedenti utilizzando valgrind
.
Questo strumento permette fra l'altro di capire se tutte le variabili sono inizializzate prima del loro uso, se accediamo a memoria gia' deallocata o mai allocata e situazioni similari
Per fare questo procedere come segue:
-g
per includere le informazioni di debugging. Ad esempio se il mio file si chiama main.c
posso compilare conbash$ gcc -Wall -pedantic -g -o prova main.c
bash$ valgrind ./prova
in questo modo, a schermo verranno riportare le infrazioni rilevate. Ad esempio, invalid read o invalid write sono accessi in lettura o scrittura a memoria non allocata o gia' deallocata.
valgrind
contiene moltissime opzioni, invitiamo gli studenti interessati ad esplorarle partendo dalsito.