Fanno eccezione gli esercizi marcati con Avanzato, che sono opzionali; per questi esercizi, il numero di asterischi indica la difficoltà prevista. Si suggerisce di affrontare gli esercizi avanzati (specialmente quelli con più di un asterisco) dopo aver completato quelli regolari, oppure più avanti nel corso.
Compilare il programma usando il comando (da riga di comando) gcc -o HelloWorld HelloWorld.c (gcc è il compilatore C del progetto GNU, in assoluto il più diffuso al mondo; l'opzione -o indica in che file mettere l'eseguibile risultante) e verificare che funzioni come atteso.
Si modifichi poi il programma definendo una macro FORMATO al posto della stringa di formato usata nella funzione printf(), e si usi la macro nella chiamata a printf().
Avanzato ***: il comando objdump consente di vedere il contenuto dei file .o. Usate objdump -t file.o per vedere i simboli definiti all'interno del file, o objdump -d file.o per vedere il codice assembler compilato dal vostro codice C. Suggerimento: compilate il codice con gcc -g -c file.c e disassemblatelo con objdump -S file.o per vedere, riga per riga, come il vostro codice C viene trasformato in Assembler.
Oltre che con #define, una macro come DEBUG può essere definita direttamente sulla riga di comando del compilatore con l'opzione -Dmacro=definizione o semplicemente -Dmacro. Si provi quindi a compilare il programma passando a gcc l'opzione -DDEBUG e senza e ad osservare il risultato.
Si usi l'opzione -E di gcc per verificare quale codice viene passato al compilatore dal preprocessore nei due casi.
Avanzato *: si configurino le proprietà del progetto Eclipse per Echo in modo da definire o meno la macro DEBUG, come si è fatto con gcc -D.
Salvo ove diversamente indicato, gli esercizi seguenti possono essere risolti lavorando su Eclipse. Si suggerisce di creare un progetto distinto per ogni esercizio.
Avanzato *: si compili lo stesso programma con il gcc, in tre varianti: senza opzioni particolari, con l'opzione -m32, con l'opzione -m128bit-long-double, e con l'opzione -m96bit-long-double. Si confrontino i risultati nei diversi casi.
Si scriva una propria implementazione di questa funzione, chiamandola strlen2(). Si scriva poi una funzione main() che, per ciascuna delle stringhe passate sulla riga di comando, stampi la stringa, la sua lunghezza calcolata da strlen() e quella calcolata da strlen2(). Si verifichi che le due funzioni restituiscano lo stesso risultato.
int a=666;
int *b=&a;
int **c=&b;
char *s="Ciao mamma!";
Il programma deve stampare, per ciascuna delle espressioni a, b, c, s, *b, *c, *s, **c, &a, &b, &c, &s, il testo dell'espressione e il suo valore numerico, usando una riga per ciascuna espressione. Si disegni (su carta) il diagramma dell'ambiente e della memoria corrispondente alle dichiarazioni in questione, usando gli indirizzi e i valori stampati dal programma. Si verifichi che, per ciascuna delle espressioni sopra indicate, la traccia della valutazione delle espressioni nel diagramma coincida con il valore effettivamente stampato dal programma.
Avanzato **: si implementi atoi2() in modo tale che essa riproduca esattamente il comportamento di atoi() anche nei casi di errore. Si esegua il comando man atoi per i dettagli su atoi().
Salvo ove diversamente indicato, gli esercizi seguenti possono essere risolti lavorando su Eclipse. Si suggerisce di creare un progetto distinto per ogni esercizio.
La funzione di libreria strlen() prende come parametro una stringa, e restituisce la sua lunghezza. Per esempio, strlen("Ciao mamma!") restituisce 11.Si scriva una propria implementazione di questa funzione, chiamandola strlen2(). Si scriva poi una funzione main() che, per ciascuna delle stringhe passate sulla riga di comando, stampi la stringa, la sua lunghezza calcolata da strlen() e quella calcolata da strlen2(). Si verifichi che le due funzioni restituiscano lo stesso risultato.
int *a=(int *)malloc(10*sizeof(int));
restituisce un puntatore a uno spazio di memoria sufficiente a contenere
10 interi. Questa operazione è analoga a dichiarare un array di
10 interi con int a[10];
(ci sono alcune differenze che
verranno discusse a lezione).
Si scriva una funzione strdup2() che, ricevuta come argomento una stringa, restituisca una copia di tale stringa, memorizzata in un'area di memoria di dimensione adeguata allocata dinamicamente. Suggerimento: si ricordi che le stringhe sono gestite come puntatori a caratteri, e che una stringa deve sempre essere terminata da un byte a 0.
int strcmp2(char *s, char *t)
che restituisca un valore minore di 0 se s<t; 0 se s=t; un valore
maggiore di 0 se s>t. In ogni caso, i confronti devono essere fatti
seguendo l'ordinamento ASCII dei caratteri; una stringa più corta
deve risultare minore di una più lunga che la abbia come prefisso
(es: "mamma"<"mammaliturchi").
Nota: questo esercizio non ha un main(), quindi non può essere eseguito direttamente (perché sia significativo, occorrerebbe scrivere i dati degli studenti all'interno della struttura dati).
Avanzato *: usando degli assegnamenti, si compilino (con dati di fantasia) alcune righe del fogliopresenze, e si scriva un main() che richiama la stampaassenti(). Si verifichi che il programma si comporta come atteso.
Si scriva poi una funzione perimetro() che, ricevuta come parametro una figura, restituisca il suo perimetro (come double). Nota: potreste voler usare la funzione di libreria sqrt(), che restituisce la radice quadrata di un numero. Tale funzione è definita nel file header di sistema math.h. Inoltre, dovete istruire il linker a unire al vostro codice la libreria matematica di sistema: per far ciò, potete usare l'opzione -lm del gcc oppure indicare, fra le proprietà del progetto Eclipse, che volete usare la libreria di nome m (Proprietà del progetto, pagina C/C++ build, scheda "Librerie", inserite m nella lista di librerie "opzione -l").
Infine, si scriva una funzione main() che dichiari e inizializzi una variabile per ciascun tipo di figura, e stampi il relativo perimetro usando la funzione definita in precedenza.
Salvo ove diversamente indicato, gli esercizi seguenti possono essere risolti lavorando su Eclipse. Si suggerisce di creare un progetto distinto per ogni esercizio.
Si crei un file stack.c contenente le definizioni delle strutture dati necessarie (suggerimento: servirà almeno un array per memorizzare gli elementi o un puntatore a memoria allocata dinamicamente con malloc(), nonché un modo per sapere quanti elementi sono contenuti nello stack, o un puntatore alla sua testa) e un file stackop.c contenente le definizioni delle seguenti funzioni: int push(int) impila un intero nello stack; restituisce 0 se l'operazione è andata a buon fine, oppure -1 in caso di errori (per esempio, non c'è spazio nello stack); int pop() toglie l'elemento in cima allo stack e lo restituisce al chiamante; ritorna 0 se lo stack è vuoto; int size() restituisce il numero di elementi presenti al momento nello stack; int empty() restituisce TRUE se lo stack è vuoto, FALSE altrimenti.
Si abbia cura di definire anche dei file .h contenenti le dichiarazioni e i prototipi corrispondenti ai file .c di cui sopra.
Infine, si scriva un file main.c contenente (ovviamente) una funzione main() che, dopo aver incluso i .h che si sono definiti e quelli di sistema eventualmente necessari, effettui alcune operazioni sullo stack, stampando i risultati e verificando che tutto funzioni come atteso.
Suggerimento: si abbia cura di usare in maniera appropriata i prototipi, le dichiarazioni extern e static per ottenere la visibilità richiesta, come visto a lezione.
Avanzato *: si scriva un main() che, usando la funzione
atoi() sviluppata in una delle precedenti esercitazioni o quella
di sistema, converta i propri argomenti di riga di comando in interi,
e li impili sullo stack con un ciclo simile al seguente:
Avanzato *: si aggiungano altre operazioni aritmetico/logiche a piacere: moltiplicazione, divisione, and, or, not, ecc. Si aggiungano poi alcuni operatori relazionali, per esempio: = (sostituisce i due elementi in cima allo stack con TRUE se i due elementi hanno lo stesso valore, con FALSE altrimenti), !=, >, <, ecc.
Esempio: il comando
Salvo ove diversamente indicato, gli esercizi seguenti possono essere risolti lavorando su Eclipse. Si suggerisce di creare un progetto distinto per ogni esercizio.
Si scrivano poi una funzione creass() che, ricevuti come argomenti i vari dati, restituisca una struttura dati opportunamente riempita, e una funzione stampass() che, ricevuta una struttura dati, ne stampi i campi in maniera leggibile usando printf(). Infine, si scriva una funzione main() che riceva sulla riga di comando i dati in questione, crei un'istanza della struttura prescelta che memorizzi i dati forniti usando creass(), e poi la stampi usando stampass().
Avanzato *: si confronti (eventualmente usando sizeof()) la dimensione della struttura dati definita sopra con quella della struttura "ovvia" (una struct con un campo intero per ogni dato) e con quella minima teorica. Si è raggiunto l'ottimo teorico? Se no, come si può compattare ulteriormente la struttura?
Avanzato **: se si è raggiunto l'ottimo teorico, si provi a trovare un modo diverso dal precedente per codificare la stessa informazione. Quale delle due versioni conduce a codice più efficiente? Quale versione conduce a codice più leggibile?
Infine, si scriva -- in un file separato -- un main() che chiami la filter() su argv, passando una per una le varie funzioni di selezione di cui sopra, e stampando il risultato.
Salvo ove diversamente indicato, gli esercizi seguenti possono essere
risolti lavorando su Eclipse. Si suggerisce di creare un progetto distinto
per ogni esercizio, scegliendo un nome che non contenga spazi o caratteri
speciali, e che sia diverso da quello dei singoli file .c che
saranno contenuti al suo interno, onde evitare conflitti di nome.
Importante: alcuni degli esercizi fanno riferimento a file,
comandi o librerie disponibili in ambiente UNIX. Si consiglia
fortemente di svolgere gli esercizi in questione su Linux (in alternativa,
gli esercizi andranno adattati al sistema operativo utilizzato).
Si usi (da shell) il comando objdump -t nomelibreria.a per esaminare il contenuto della libreria libcrypt.a. Questo comando mostra tutti i nomi dei simboli definiti da ciascun file .o presente all'interno della libreria; fra questi, i nomi di funzione sono identificati da un flag F accanto al nome (si noti anche che, per convenzione, i nomi di funzione inizianti per "_" si riferiscono a funzioni interne, non destinate ad essere chiamate da programmi esterni).
Armati da queste informazioni, si usi il comando man nomefunzione per capire quali, fra le funzioni definite da libcrypt.a, sono documentate e destinate ad essere chiamate dai programmi utente. Si scriva un programma C che dimostra l'uso di una di queste funzioni (si scelga pure quella che sembra più semplice da usare) e lo si esegua, verificando che i risultati siano conformi a quanto atteso. Suggerimento: si ricordi che occorrerà includere il file .h che definisce la funzione usata, e compilare passando l'opzione -lcrypt al compilatore.
Formule di conversione delle temperature |
||
Celsius/Kelvin | Kelvin = Celsius + 273.15 | Celsius = Kelvin - 273.15 |
Celsius/Fahrenheit | Celsius = 5/9 * (Fahrenheit -32) | Fahrenheit =( Celsius/(5/9)) + 32 |
Il programma deve stampare la tabella di conversione per temperature che vanno da -10°C a +32°C, a intervalli di 2°C; si usi per i valori numerici una precisione di due cifre dopo la virgola. La tabella deve avere tre colonne, una per ciascuna scala, e una riga di intestazione in cima, recante per ogni colonna il nome per esteso della scala di temperatura corrispondente; le tre colonne devono avere la stessa larghezza, e i valori numerici devono essere centrati nella colonna di appartenenza e allineati alla virgola decimale.
Salvo ove diversamente indicato, gli esercizi seguenti possono essere
risolti lavorando su Eclipse. Si suggerisce di creare un progetto distinto
per ogni esercizio, scegliendo un nome che non contenga spazi o caratteri
speciali, e che sia diverso da quello dei singoli file .c che
saranno contenuti al suo interno, onde evitare conflitti di nome.
Importante: alcuni degli esercizi fanno riferimento a file,
comandi o librerie disponibili in ambiente UNIX. Si consiglia
fortemente di svolgere gli esercizi in questione su Linux (in alternativa,
gli esercizi andranno adattati al sistema operativo utilizzato).
Per esempio, il comando cp2 cp2.c cp2.copy, eseguito nella directory in cui si trova il sorgente di cp2, ne crea una copia identica sotto il nome cp2.copy. Si usi il comando di Shell diff cp2.c cp2.copy (vedere man diff per dettagli sull'uso di diff) per verificare che i due file siano realmente identici quanto al contenuto. Suggerimento: cp2 deve essere in grado di copiare file di lunghezza qualunque, non necessariamente multipla di qualche valore particolare, e di contenuto qualunque. Si consideri con cura quali funzioni di I/O usare allo scopo.
Per esempio, si provino i seguenti casi con il comando time cp2 /usr/local/bin/mplayer ~/test:
Per esempio, se n=1 flower rima con zipper (il frammento di rima è er), mentre se n=2, flower rima con power (il frammento di rima è ower). Il comando
È possibile affrontare l'esercizio in (almeno) due modi:
Avanzato ***. L'editor programmabile così definito non è in grado di scrivere byte con valore \0 o \n (perché?); si introduca un meccanismo di escape per il comando w che consenta di scrivere caratteri arbitrari all'interno del file.
Salvo ove diversamente indicato, gli esercizi seguenti possono essere
risolti lavorando su Eclipse. Si suggerisce di creare un progetto distinto
per ogni esercizio, scegliendo un nome che non contenga spazi o caratteri
speciali, e che sia diverso da quello dei singoli file .c che
saranno contenuti al suo interno, onde evitare conflitti di nome.
Importante: alcuni degli esercizi fanno riferimento a file,
comandi o librerie disponibili in ambiente UNIX. Si consiglia
fortemente di svolgere gli esercizi in questione su Linux (in alternativa,
gli esercizi andranno adattati al sistema operativo utilizzato).
Suggerimento: si ricordi che occorrerà informare il chiamante riguardo alla lunghezza dell'array restituito.
Avanzato *: si implementi la split usando solo funzioni di libreria, e in particolare evitando di usare l'operatore di indirezione * (si può invece usare nelle dichiarazioni di tipo). Suggerimento: possono essere utili le funzioni strspn() e strcspn().
Si scriva poi un main() con cui verificare il funzionamento della split().
Suggerimenti: Potrà essere necessario inserire dei ritardi all'interno del programma per evitare che lo scorrimento sia troppo veloce; si usino a questo fine le funzioni sleep() o usleep(), che sospendono l'esecuzione per un tempo indicato, o un ciclo for a vuoto (ma attenzione: il compilatore potrebbe ottimizzarlo eliminandolo...).
Per ristampare sopra una riga già stampata si termini la stampa con il carattere \r (ritorno a capo) invece che con \n (nuova linea e a capo).
Esempio: se il file tg4.txt contiene
Salvo ove diversamente indicato, gli esercizi seguenti possono essere
risolti lavorando su Eclipse. Si suggerisce di creare un progetto distinto
per ogni esercizio, scegliendo un nome che non contenga spazi o caratteri
speciali, e che sia diverso da quello dei singoli file .c che
saranno contenuti al suo interno, onde evitare conflitti di nome.
Importante: alcuni degli esercizi fanno riferimento a file,
comandi o librerie disponibili in ambiente UNIX. Si consiglia
fortemente di svolgere gli esercizi in questione su Linux (in alternativa,
gli esercizi andranno adattati al sistema operativo utilizzato).
Per esempio (ma il formato delle date è a piacere),
|
produce in output |
|
Il programma deve usare efficientemente il dizionario di sistema.
Avanzato ** Il programma deve mantenere alcune statistiche globali, quali: numero di parole comprese nel dizionario, numero di parole controllate, numero di parole corrette, numero di parole errate, ecc. All'uscita (intenzionale) dal programma, in qualunque condizione si verifichi, lo spell checker deve stampare una tabellina opportunamente formattata contenente tali statistiche.
Durante questa esercitazione ciascuno studente potrà iniziare il progetto finale del corso, con l'assistenza del docente e degli esercitatori.