Traccia della lezione Lez01: Lucidi 4/10: La logica degli esempi è mostrare che tutte le applicazioni informatiche si riducono, in ultima analisi, all'elaborazione di una stringa di simboli per produrre un'altra stringa di simboli. Questo può essere ovvio quando si lavora su lettere, meno ovvio se si lavora su parole, ancora meno su immagini e così via. L'esempio estremo sono gli schermi touchscreen che convertono movimenti delle dita in stringhe di 0 e 1, ai quali un programma risponde con stringhe di 0 e 1 che appaiono all'utente come grafica, suoni o persino movimenti fisici (se il dispositivo comanda degli attuatori). Lucido 4: L'algoritmo è molto semplice: i simboli sono singoli numeri, elaborati uno alla volta Lucido 5: I simboli elementari qui sono parole; l'algoritmo è più sofisticato: non elabora un simbolo alla volta, ma studia la struttura della frase (punteggiatura, sostituzione di tempi verbali). Lucido 6: Il passaggio da 2D a 1D avviene in varie fasi: - discretizzazione di un'immagine continua (griglia) - riduzione da griglia a stringa per righe da sinistra a destra (convenzione occidentale: ogni codifica è convenzionale) Lucido 13: La "scatola nera" PROCESSORE riceve dall'alto il programma (PROGRAM.EXE) in formato binario e in base ad esso trasforma i dati, che riceve da sinistra (in formato binario), nei risultati, che emette a destra (in formato binario). Lucido 15: Incapsuliamo il processore premettendo un ASSEMBLER, che riceve PROGRAM.ASM, e due traduttori da simboli (per esempio numeri o caratteri ASCII) a stringhe binarie e da stringhe binarie a simboli Lucido 19: Incapsuliamo la macchina assembly premettendo un COMPILER, che riceve PROGRAM.C e lo traduce in PROGRAM.ASM. Lucido 22: Le residue machine-dependency del C89 fanno sì che parte del legame ingresso-uscita dipenda ancora dal processore. Quindi lo schemino sull'incapsulamento del lucido 19 non è perfettamente corretto: in effetti, il C consente in parte l'accesso del programma PROGRAM.C al livello del processore, cioè alla rappresentazione binaria. Lucido 28: Viste le tre fasi della traduzione, avviamo il computer: 1) apertura di una finestra terminale DOS (Start/Esegui/CMD) 2) alcuni comandi DOS essenziali: DIR per avere una lista dei file contenuti nella directory corrente CD per spostarsi in un'altra directory MD per creare una sottodirectory RD per cancellare una sottodirectory (vuota) MORE per stampare a video un file, una pagina alla volta: SPACEBAR per andare alla successiva CTRL-C per uscire Minuscole e maiuscole sono equivalenti per il sistema operativo Windows (invece sono diverse in C e in UNIX/Linux). Redirezione: - dell'output: facendo seguire a un comando ">" e il nome di un file di testo, il risultato del comando viene salvato in tale file. - dell'output in append: facendo seguire a un comando ">>" e il nome di un file di testo, il risultato del comando viene accodato a tale file. - dell'input: facendo seguire a un comando "<" e il nome di un file di testo, il programma ricevera' gli ingressi da tale file anziché da tastiera 3) scaricare e copiare i file di esempio della lezione in una directory di lavoro e spostarsi in quella directory 4) NOTEPAD HELLO1.C per aprire e leggere il primo esempio Ora si provi a lanciare il comando GCC. Se il file GCC.EXE è nella directory di lavoro o in una di quelle elencate nel path di sistema (che lista le directory dove il sistema operativo cerca i comandi), si ottiene in risposta: "gcc: no input files", perché il compilatore si avvia e indica che manca il codice da compilare. Se invece si ottiene un messaggio di errore, secondo il quale GCC non e' un comando, il motivo è che il file GCC.EXE non è presente nella directory di lavoro né nel path di sistema. Il file batch ADDEVTOPATH.BAT aggiunge la directory che contiene il compilatore GCC al path di sistema (si presuppone che GCC.EXE sia in C:\DEV-CPP\BIN; se non lo e', bisogna modificare il file batch). Lanciando il file batch, si crea anche un file RESETPATH.BAT, che consente in ogni momento di ripristinare il path di sistema originale. Se si chiude la finestra terminale e se ne apre un'altra, il path di sistema torna quello originale, senza l'aggiunta. Lucido 29: Apriamo il codice di esempio HELLO1.C - introduciamo lo scopo e il significato generale del programma (stampare un saluto racchiuso in una cornicetta di asterischi) - si noti che il codice e' leggibile - non si badi alle singole istruzioni perché ne parleremo al momento opportuno - si badi invece a riconoscere in esso le varie sezioni indicate La struttura di un listato C è in effetti più libera, ma per capire i gradi di libertà bisogna capire a fondo il processo di compilazione. Questa struttura funziona sicuramente, e quindi ci atterremo ad essa. La struttura richiama molto quella delle RICETTE DI CUCINA: - prototipi delle funzioni = ricette di componenti secondari (maionese, pasta sfoglia, battuto...) - parametri delle funzioni = ingredienti delle ricette secondarie - parte dichiarativa = elenco dei contenitori (piatti, terrine...) - parte esecutiva = elenco delle operazioni Lucido 30: - si possono seguire diversi stili di commento per i prototipi; i più usati sono: /************/ /* */ /* commento */ /* */ /************/ /* * * commento * */ /* commento */ Lucido 31: - <> racchiude un file che si trova nelle directory del compilatore - i caratteri racchiusi fra apici ' ' sono quelli che vanno intesi come caratteri, mentre gli altri compongono parole chiave, nomi di variabili e istruzioni - le stringhe di caratteri racchiuse fra virgolette "" vanno intese come stringhe di caratteri Lucido 32: dopo le direttive di precompilazione PRECOMPILAZIONE: 1) lanciare: gcc HELLO1.c -E: stampa a video per averlo su file: gcc hello1.c -E -o hello1.txt oppure con la redirezione: gcc hello1.c -E > hello1.txt Il primo modo usa gcc, il secondo usa il sistema operativo 2) notepad hello1.txt a) sono spariti i commenti b) è comparso tutto il contenuto del file stdio.h (printf, ecc...) c) sono scomparse le #define e i simboli sono stati sostituiti dai corrispondenti valori: è una meccanica sostituzione di testo d) vedremo più avanti un esempio di compilazione condizionale 3) compilando HELLO2.c, osserviamo l'azione ricorsiva di #include - "" racchiude un file che si trova nella directory corrente Si può illustrare anche la ricorsione su #define - sostituendo in HELLO2.C la riga l1 = LUNGHEZZA con l1 = NUMERO - aggiungendo #define NUMERO LUNGHEZZA in HELLO2.C FC HELLO1.TXT HELLO2.TXT mostra che hello1.txt e hello2.txt sono praticamente identici CLS consente di svuotare la schermata 4) HELLO3.C fornisce un esempio di compilazione condizionale; - si cambi il simbolo che segue #ifdef da LUNGHEZZA a LARGHEZZA e si osservi la differenza sia su HELLO3.TXT sia nell'output 5) HELLO4.C con i file IOCREMA.C e IOCREMA.H sono un esempio di progetto complesso, costituito da: - file di testo prodotti dal programmatore (HELLO4.C) - file di testo prodotti da altri (IOCREMA.C e IOCREMA.H) - file di testo forniti dal venditore del compilatore (STDIO.H, che consente di leggere da tastiera e scrivere a video) Nelle prossime lezioni, useremo sempre la libreria IOCREMA per poter disporre di funzioni di scrittura e lettura che abbiano nomi semplici e chiari. COMPILAZIONE: Il comando è per precompilare e compilare (prima e seconda fase): gcc -c hello1.c -o hello1.o Se non si specifica l'output, il compilatore produce automaticamente hello1.o Cambia solo l'estensione: la relazione codice-oggetto è uno a uno. - aprendo il file oggetto con notepad, si vede che non e' un testo (anche se contiene alcuni pezzi di testo, precisamente i nomi simbolici delle funzioni, non le macro) COLLEGAMENTO: Per trasformare gli oggetti in eseguibili la linea di comando è: gcc hello1.o -o hello1.exe gcc hello4.o iocrema.o -o hello4.exe (si osservi l'errore che si ottiene fornendo un solo file oggetto) Se non si specifica l'output, il linker produce automaticamente a.exe, che e' un nome bizzarro, ma il linker può ricevere molti file oggetto, e quindi non è ovvio che nome debba dare all'eseguibile (mentre il compilatore ne riceve uno solo, per cui il nome è ovvio). Si può fare precompilazione, compilazione e linking tutto in una volta: gcc hello1.c -o hello1.exe gcc hello4.c iocrema.c -o hello4.exe Anche qui, se non si specifica -o, il procedimento genera a.exe Lucido 34: - si noti che i dati sono racchiusi fra parentesi - il risultato viene specificato dal tipo della funzione, che precede il nome. Qui il tipo è "void", a indicare che non ci sono risultati. - si noti che la dichiarazione di funzione è leggibile come una frase ("stampa il carattere c ripetendolo num volte") Questo è un esempio di stile: aiuta il lettore a capire che cosa fa il codice, e a rendersi conto se fa tutto e solo quel che deve. La direttiva #include spesso include file header che contengono prototipi di funzioni definite in librerie esterne. Questo consente di usarle senza dover ogni volta citare il prototipo. Lucido 41: - le procedure secondarie hanno la stessa struttura del main: intestazione, parte dichiarativa, parte esecutiva - perché ripetere i nomi delle funzioni (prototipi e definizioni)? Affinché il compilatore possa scorrere il testo dall'alto in basso in una sola passata: quando trova un'istruzione definita da utente (chiamata di funzione) non sa ancora che cosa fa la funzione, ma grazie al prototipo può subito controllare la correttezza di ciò che riceve (dati) e di ciò che restituisce (risultati). Tanto basta. Esercizi: - introdurre commenti nei vari formati in vari punti del codice - sostituire i nomi simbolici con valori espliciti - sostituire i nomi simbolici con nomi di variabili - cambiare i valori delle costanti simboliche - definire altre costanti simboliche (meglio che sostituire costanti esplicite punto per punto lungo l'intero codice)