Indice
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 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'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 comandogcc
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 <errno.h>
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 interoi
nell'intervallo[0,K-1]
e stamparle sullo standard output - Per ogni
i
calcolare la percentuale di occorrenze sulle occorrenze totali (c_i
divisoN
) 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 chiamamain.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 <stdio.h> #include <math.h> 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 <math.h>
? 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))