====== Esercitazione 2 ====== Dove si sperimenta qualche tool e si approfondiscono alcune caratteristiche del C sequenziale: errno, perror creazione di librerie e generazione di numeri casuali ===== Esercizio 1: Preprocessing, compilazione e linking ===== Seguire l'esempio dei lucidi visti nella lezione di proprocessing, compilazione e linking verificando i vari passi utilizzando il programma esempio presentato ed i comandi descritti nei {{:informatica:sol:laboratorio15:esercitazionib:c006prepcomplinkong.pdf|lucidi presentati a lezione}} ===== Esercizio 2: Creare ed usare una libreria di liste ===== Realizzare una libreria che fornisce le operazioni su liste implementate negli esercizi 2 e 3 dell'[[informatica:sol:laboratorio16:esercitazionib:esercitazione1|esercitazione 1.]] In C tipicamente per realizzare una libreria si difinisce un file ''.h'' con i prototipi ed i tipi messi a disposizione dalla libreria e si fornisce il codice pre-compilato in un opportuno file di libreria ''.a''. Nel nostro caso l'header si chiamera' ''lista.h'' e la libreria si chiamera' ''libList.a''. Procedere nel modo seguente: * Definire un file ''lista.h'' che contiene i tipi ed i prototipi delle funzioni implementate * Definire un file ''lista.c'' che contiene il codice che implementa le funzioni. * Generare il modulo oggetto ''lista.o'' da inserire nella libreria con il comando ''gcc'' come segue $ gcc -Wall -pedantic -c lista.c * Creare la libreria con i lcomando ''ar'' come segue $ ar -r libList.a lista.o Quando la creazione e' andata a buon fine creare un file ''main.c'' che contenga uno dei main di test sviluppati negli esercizi precedenti e compilarlo utilizzando la libreria. In particolare : * il main di test dovra' includere il file ''lista.h'' * in fase di compilazione utilizzare la libreria sviluppata con $ gcc -Wall -pedantic main.c -lList -L. dove l'opzione ''-l'' indica il nome della libreria da usare in fase di linking e l'opzione ''-L'' specifica la directory dove cercarla (oltre alle directory standard tipo ''/usr/lib''). . ===== Esercizio 3: Manipolare ''errno'' e uso di ''perror()'' ===== In C, la maggior parte delle funzioni di libreria che segnalano un errore settano anche la variabile globale ''errno'' con dei codici definiti da diversi standard. I codici sono valori interi, definiti da opportune macro. Per vadere il loro valore eseguire bash$ man errno Dopo l'esecuzione di una funzione di libreria che imposta ''errno'' e' possibile chiamare la funzione di libreria ''perror()'' che ispeziona il valore di ''errno'' e trasforma il valore numerico in un messaggio testuale comprensibile all'utente (vedere ''man perror'' per il suo uso). E' possibile manipolare ''errno'' da programma includendo l'header ''errno.h'' con #include L'esercizio richiede di essegnare a errno i valori ''EINTR EPERM EBUSY'' e stampare i corrispondenti messaggi di errore usando ''perror()''. ===== Esercizio 4: Generazione numeri casuali con ''rand()'' ed ''srand()'' ===== Generare ''N'' numeri casuali interi nell'intervallo ''[0,K-1]'' utilizzando le funzioni ''rand()'' ed ''srand()''. N e K sono definiti con delle opportune ''#define''. Per ''N==100'' e ''K==4'' eseguire quanto segue. * Calcolare il numero di occorrenze ''c_i'' di ciascun intero ''i'' nell'intervallo ''[0,K-1]''e stamparle sullo standard output * Per ogni ''i'' calcolare la percentuale di occorrenze sulle occorrenze totali (''c_i'' diviso ''N'') e stamparle sullo standard output * Esaminare le occorrenze degli interi nell'intervallo e verificare che sono piuttosto vicine fra di loro * Che distribuzione hano i numeri generati ? ===== Esercizio 5: Generazione numeri casuali con ''rand_r()'' ===== Ripetere l'esecizio 4 utilizzando la funzione ''rand_r()''. A che serve questa funzione ? Quali sono le principali differenze ? (//Suggerimento//: partire dall'analisi accurata del ''man'') ===== Esercizio 6: Verificare gli accessi in memoria: valgrind ===== Verificare la correttezza degli accessi ai puntatori compiuti dalle funzioni su liste di interi della libreria libList.a 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: * compilare il file da verificare con opzione ''-g'' per includere le informazioni di debugging. Ad esempio se il mio file si chiama ''main.c'' posso compilare con bash$ gcc -Wall -pedantic -g -o prova main.c * eseguire 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. ===== Esercizio 7: Ancora su -- 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 8: 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 9: 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))