Sistemi operativi, A.A. 2011/2012 (M. Cesati) Scaletta esercitazione #3, 22.03.2012 1 Esercizio: scrivere un programma che legge un numero intero dalla linea di comando e stampa in standard output il suo quadrato ed il suo cubo +-------------------------------------------+ |/* Esempio di un *brutto* programma */ | |#include | |int main(int argc, char *argv[]) { | |int v; | |v=atoi(argv[1]); | |printf("quadrato=%d cubo=%d\n",v*v,v*v*v);}| +-------------------------------------------+ 1.1 Compiliamo il programma: +------------------------+ |$ gcc -o es1_v1 es1_v1.c| +------------------------+ 1.2 Proviamo il programma: +--------------------+ |$ ./es1_v1 4 | |quadrato=16 cubo=64 | +--------------------+ 1.3 Sembra funzionare... ma questo programma ha gravi bug ed altri difetti! 2 Problema #1: il programma non e' indentato correttamente 2.1 Difficile seguire la logica e capire il programma 2.2 Scrivere un programma in modo ordinato indica che il programmatore possiede il giusto atteggiamento mentale: ordine, pazienza, riflessione 2.3 Esistono diversi stili di indentazione: l'importante e' sceglierne uno e utilizzarlo in modo consistente (soprattutto all'interno dello stesso progetto) 3 Strumento per indentare un programma: "indent" 3.1 Molte opzioni disponibili utilizzare "man indent" per studiarle 3.2 Un insieme di opzioni 'gradevoli': 3.2.1 "-kr" stile del libro di testo di Kernighan-Ritchie 3.2.2 "-nut" converte tutti i tab (ASCII 9) in spazi 3.2.3 "-i4" linee indentate con quattro spazi 3.2.4 "-ts4" tab (ASCII 9) equivalente a quattro spazi 3.3 Due modalita' di invocazione: 3.3.1 Come filtro: $ indent -kr -nut -i4 -ts4 < es_v1.c 3.3.2 Come editor: $ indent -kr -nut -i4 -ts4 es_v1.c 3.3.2.1 Crea un file di backup con lo stesso nome + "~" 4 Problema #2: il processo di compilazione non e' stato corretto 4.1 Non sono state usate alcune opzioni essenziali 4.1.1 Ottimizzazione del codice ("-O") 4.1.2 Attivazione dei "warning" del compilatore 4.2 Compilazione corretta: +--------------------------------------------------------------+ |$ gcc -Wall -Wextra -O2 -o es1_v2 es1_v2.c | |es1_v2.c: In function 'main': | |es1_v2.c:6: warning: implicit declaration of function 'atoi' | |es1_v2.c:3: warning: unused parameter 'argc' | |es1_v2.c:8: warning: control reaches end of non-void function | +--------------------------------------------------------------+ 4.3 Abbiamo scoperto nuovi potenziali problemi del programma! 5 Problema #3: Manca il prototipo della funzione atoi() 5.1 Se utilizziamo una funzione senza aver indicato un prototipo al compilatore (eventualmente in un header file), allora il compilatore 5.1.1 Accetta ogni tipo di argomento per la funzione (non c'e' controllo) 5.1.2 Si aspetta che la funzione restituisca un numero intero 5.2 Per determinare l'header file da includere: "man atoi" 5.3 Soluzione del problema: includere il file header: #include 5.3 Nuova compilazione: +--------------------------------------------------------------+ |$ gcc -Wall -Wextra -O2 -o es1_v3 es1_v3.c | |es1_v3.c: In function 'main': | |es1_v3.c:4: warning: unused parameter 'argc' | |es1_v3.c:9: warning: control reaches end of non-void function | +--------------------------------------------------------------+ 6 Problema #4: Un parametro della funzione main() non e' utilizzato 6.1 "argc" indica quanti parametri sono stati passati alla funzione 6.1.1 Un programma invocato senza parametri restituisce il valore 1, perche' argv[0] rappresenta sempre il nome del file utilizzato per lanciare il programma 6.2 Perche' e' un problema non leggere argc? Proviamo a lanciare il programma senza parametri: +-------------------+ |$ ./es1_v3 | |Segmentation fault | +-------------------+ 6.3 Il messaggio "Segmentation fault" e' stato inviato dalla shell di comandi 6.3.1 Significa che il processo lanciato si e' concluso in modo anormale a causa di un segnale "SIGSEGV" ricevuto 6.3.2 Il segnale SIGSEGV e' tipicamente generato dal nucleo del SO quando il processo tenta di compiere una operazione in memoria non lecita 6.4 Se il programma viene invocato senza argomenti allora argv[1] non rappresenta l'indirizzo di una stringa valida: e' NULL 6.4.1 L'errore e' nella conversione della stringa all'indirizzo NULL 6.5 Soluzione del problema: controllare il numero di argomenti passati al programma leggendo argc: +-----------------------+ |if (argc > 1) { | | v = atoi(argv[1]); | | printf(... | |} | +-----------------------+ 6.5.1 Compilando e provando: +-----------------------------------------------------------------+ |$ gcc -Wall -Wextra -O2 -o es1_v4 es1_v4.c | |es1_v4.c: In function 'main': | |es1_v4.c:12: warning: control reaches end of non-void function | |$ es1_v4 | |$ (non stampa niente, ma almeno non termina con errore) | +-----------------------------------------------------------------+ 6.5.2 Dovremmo aiutare l'utente a capire come usare il programma con un messaggio esplicativo: +--------------------------------------------+ |} else | | printf("Usage: %s \n", argv[0]);| +--------------------------------------------+ 6.5.2.1 Compilando e provando: +---------------------------------------------------------------+ |$ gcc -Wall -Wextra -O2 -o es1_v5 es1_v5.c | |es1_v5.c: In function 'main': | |es1_v5.c:13: warning: control reaches end of non-void function | |$ ./es1_v5 | |Usage: ./es1_v5 | +---------------------------------------------------------------+ 7 Problema #5: la funzione main dovrebbe restituire un valore "int", ma si conclude senza "return ;" 7.1 Dopo aver eseguito il programma, il codice (livello) d'errore e' casuale: +----------------------+ |$ ./es1_v5 4 | |quadrato=16 cubo=64 | |$ echo $? | |20 | |$ ./es1_v5 3 | |quadrato=9 cubo=27 | |$ echo $? | |19 | +----------------------+ 7.2 Soluzione: inserire l'istruzione "return EXIT_SUCCESS;" se il programma e' lanciato con almeno un argomento, "return EXIT_FAILURE;" altrimenti: +---------------------------------------------+ |$ gcc -Wall -Wextra -O2 -o es1_v6 es1_v6.c | |$ ./es1_v6 4 | |quadrato=16 cubo=64 | |$ echo $? | |0 | |$ ./es1_v6 | |Usage: ./es1_v6 | |$ echo $? | |1 | +---------------------------------------------+ 8 Il compilatore non da' piu' alcun "warning", dunque il programma e' bug-free? 8.1 Ovviamente no! Problema #6: mancata validazione dell'input dell'utente -+-----------------------+ |$ ./es1_v6 ciao mondo | |quadrato=0 cubo=0 | |$ ./es1_v6 4baci | |quadrato=16 cubo=64 | +-----------------------+ 8.2 Il programma dovrebbe avvisare l'utente che l'argomento sulla linea comando non e' un numero valido! 8.3 Da "man atoi": atoi() non e' in grado di segnalare gli errori alla funzione invocante 8.3.1 Al suo posto possiamo utilizzare la funzione strtol() +---------------------------------------------------------------------+ |#include | |#include | |#include | | | |int main(int argc, char *argv[]) | |{ | | int v; | | | | if (argc > 1 && argv[1][0] != '\0') { | | char *p; | | | | errno = 0; | | v = (int) strtol(argv[1], &p, 0); | | if (errno != 0 || *p != '\0') { | | printf("%s: Invalid argument '%s'\n", argv[0], argv[1]); | | return EXIT_FAILURE; | | } | | | | printf("quadrato=%d cubo=%d\n", v * v, v * v * v); | | return EXIT_SUCCESS; | | } | | printf("Usage: %s \n", argv[0]); | | return EXIT_FAILURE; | |} | +---------------------------------------------------------------------+ 9 Problema #7: mancata distinzione tra il ruolo dello standard output e dello standard error +----------------------------------------------------------------------+ |#include | |#include | |#include | | | |int main(int argc, char *argv[]) | |{ | | int v; | | | | if (argc > 1 && argv[1][0] != '\0') { | | char *p; | | | | errno = 0; | | v = (int) strtol(argv[1], &p, 0); | | if (errno != 0 || *p != '\0') { | | fprintf(stderr, "%s: Invalid argument '%s'\n", argv[0], | | argv[1]); | | return EXIT_FAILURE; | | } | | | | printf("quadrato=%d cubo=%d\n", v * v, v * v * v); | | return EXIT_SUCCESS; | | } | | fprintf(stderr, "Usage: %s \n", argv[0]); | | return EXIT_FAILURE; | |} | +----------------------------------------------------------------------+ 10 Problema #8: mancato controllo dell'overflow delle variabili 10.1 Anche se v e' entro la dimensione di un "int", v*v oppure v*v*v potrebbe non esserlo: +-----------------------------------+ |$ ./es1_v8 10000 | |quadrato=100000000 cubo=-727379968 | +-----------------------------------+ 10.2 Il controllo dell'overflow, nel caso della moltiplicazione, non e' facile 10.3 Il sistema piu' semplice e' promuovere i valori con un 'cast' alla dimensione massima prima di fare l'operazione: +-------------------------------------------------------------------+ |#include | |#include | |#include | | | |int main(int argc, char *argv[]) | |{ | | int v; | | long long llv2, llv3; | | | | if (argc > 1 && argv[1][0] != '\0') { | | char *p; | | | | errno = 0; | | v = (int) strtol(argv[1], &p, 0); | | if (errno != 0 || *p != '\0') { | | fprintf(stderr, "%s: Invalid argument '%s'\n", argv[0],| | argv[1]); | | return EXIT_FAILURE; | | } | | | | llv2 = (long long) v *v; | | llv3 = llv2 * v; | | | | printf("quadrato=%lld cubo=%lld\n", llv2, llv3); | | return EXIT_SUCCESS; | | } | | fprintf(stderr, "Usage: %s \n", argv[0]); | | return EXIT_FAILURE; | |} | +-------------------------------------------------------------------+ 10.3.1 Dopo la compilazione: +--------------------------------------+ |$ es1_v9 10000 | |quadrato=100000000 cubo=1000000000000 | +--------------------------------------+ 11 Richiami sulla visibilita' e persistenza delle variabili 11.1 Variabili automatiche 11.2 Variabili globali 11.3 Variabili automatiche statiche 11.4 Variabili globali statiche 11.5 Non e' una buona pratica usare senza giustificato motivo le variabili globali! 12 Richiami sui puntatori 12.1 Operatori * e & ==================