====== Esercitazione 3 ====== Dove si approfondisce la conoscenza delle fasi di preprocessing, compilazione e linking, si prova ad implementare delle liste generiche in C e si studiano piu' approfonditamente le caratteristiche dei ''valgrind'' ed alcune opzioni di ''gcc''. Rassegnadoci all'idea che un programma che passa tutti test puo' nondimeno essere scorretto .... ===== Esercizio 1: Getting started -- Preprocessing, compilazione e linking ===== 1) Compilare ed eseguire il seguente programma: #include #include int main (void) { double x=3.0; printf("Radice = %f\n",sqrt(x)); return 0; } salvato nel file //ff.c// con gcc -Wall -pedantic ff.c Chi segnala un errore? E' fallita la fase di preprocessing, la compilazione o il linking? Cosa contine il modulo oggetto se specifico l'opzione -c? Come si risolve il problema? 2) Cosa accade se eliminiamo la linea #include ? A questo punto cosa va storto? Sapete interpretare i messaggi a video e stabilire chi li ha scritti e perche'? Viene generato l'eseguibile? 3) Generare il modulo oggetto con gcc -Wall -pedantic -c ff.c Utilizzare //objdump, nm, readelf// per capire cosa contengono la tabella di rilocazione, la tabella dei simboli esportati ed esterni, le sezioni data, BSS e codice. (utilizzare il man e cercare su google). 4) Usare l'opzione //-E// e la //-S// del gcc: che cosa succede? Cosa accade specificando il flag -g assieme a -S? ===== Esercizio 2: Macro con parametri, macro SOMMA ===== Usare le macro con parametri per definire una macro che somma (operatore +) i propri argomenti #define SOMMA(X,Y,Z) ...... e testarla in un opportuno main. Valutare le differenze con una funzione di prototipo int SOMMA(int X,int Y, int Z); ===== Esercizio 3: Macro con parametri, macro FATTORIALE ===== Scrivere una macro con parametri che calcoli il fattoriale di un numero N, passato come parametro e ne stampi il risultato. Ad esempio, posso utilizzare la macro per calcolare il fattoriale di 4+1 con FATTORIALE(4+1) La macro non deve fare assunzioni su come verranno passati i parametri. Che accade annidando due chiamate della macro? Ad esempio FATTORIALE(FATTORIALE(4+1)) ===== Esercizio 4. Liste generiche in C ===== In questo esercizio si richiede di realizzare alcune funzioni che lavorano su liste generiche in C. Una lista generica e' rappresentata con la seguenti struct typedef struct elem { /** chiave */ void * key; /** informazione */ struct elem * next; } elem_t; typedef struct { /** la testa della lista */ elem_t * head; /** la funzione per confrontare due chiavi */ int (* compare) (void *, void *); /** la funzione per copiare una chiave */ void * (* copyk) (void *); } list_t; la prima struttura (''elem_t'') rappresenta un nodo della lista generica. Ogni nodo contiene una chiave ''key'' che puo' avere tipo qualsiasi. La seconda struttura (''list_t'') permette di definire una particolare lista a partire da quella generica. Per farlo bisogna fornire due funzioni: * ''compare'' permette di confrontare due chiavi, ritorna 0 se sono uguali ed un valore diverso da 0 altrimenti * ''copyk'' crea una copia della chiave (allocando la memoria necessaria) e ritorna il puntatore alla copia (se tutto e' andato bene) o NULL (se si e' verificato un errore) Si chiede di realizzare le funzioni che permettono di creare/distruggere una lista generica e quelle per inserire ed estrarre un elemento generico. Realizzare un main di test che prova ad istanziare la lista in due versioni: una prima a valori interi usando le seguenti funzioni per il confronto e la copia: /** funzione di confronto per lista di interi \param a puntatore intero da confrontare \param b puntatore intero da confrontare \retval 0 se sono uguali \retval p (p!=0) altrimenti */ int compare_int(void *a, void *b) { int *_a, *_b; _a = (int *) a; _b = (int *) b; return ((*_a) - (*_b)); } /** funzione di copia di un intero \param a puntatore intero da copiare \retval NULL se si sono verificati errori \retval p puntatore al nuovo intero allocato (alloca memoria) */ void * copy_int(void *a) { int * _a; if ( ( _a = malloc(sizeof(int) ) ) == NULL ) return NULL; *_a = * (int * ) a; return (void *) _a; } e una seconda che ha come chiavi stringhe usando analoghe funzioni per la copia ed il confronto. ===== Esercizio 5. Approfondiamo l'uso di valgrind (e alcune opzioni utili di gcc ...) ===== Compilare ed eseguire il codice seguente usando valgrind #include #include #include #include #define N 5 int main(void) { int * a; int i; if ( ( a = malloc(N*sizeof(int))) == NULL ) return EXIT_FAILURE; srand(time(NULL)); i=0; while (i che problemi vengono segnalati ? Perche' ? Provare a compilare (dopo averlo salvato in file.c) usando le seguenti opzioni di "gcc" che permettono di rilevare altri errori statici: bash$ gcc -O -pedantic -Wall -Wextra -Wformat=2 -ggdb -o exe file.c viene segnalato qualcosa ? Perche' ?