Seminario di C Appendici Paride Dominici
Indice Appendice A - Allocazione dinamica della memoria... 2 Appendice B - Progettazione lineare di un programma... 4 Appendice C - Utilizzo dei file in modalità binaria... 5 Appendice D - Utilizzo del programma make... 7 Appendice E - Tabella codici ASCII Standard (0 127)... 9 Appendice F Tipi più comuni nella generazione delle CGI... 10 1
Appendice A - Allocazione dinamica della memoria Per rendere più efficiente e meno pesante un programma in C si utilizza una tecnica particolare che è detta allocazione dinamica della memoria. Generalmente all ingresso di una funzione il compilatore si occupa di riservare la quantità di memoria di cui ha bisogno la funzione per le variabili locali. Ad esempio : void funzione() { int a; char stringa[200];... } All ingresso di questa funzione il compilatore alloca cioè riserva (considerando che l int sia a 4 byte) 204 byte per le variabili locali in una zona di memoria preposta a questo. All uscita della funzione, sarà il compilatore stesso a liberare cioè a rendere di nuovo disponibile la memoria che si era riservato. Questa modalità di gestione della memoria è molto comoda, perché non ci costringe a pensare ai problemi di memoria, ma in alcuni casi può essere comodo poter avere il controllo su queste operazioni. Le due funzioni principali del C per fare l allocazione dinamica della memoria sono la malloc (per allocare la memoria) e la free (per liberare la memoria). In pratica con queste due funzioni (o altre similari che vedremo in C++) siamo noi a chiedere al sistema operativo di riservarci una certa quantità di memoria (che siamo noi a specificare) e di dirci semplicemente dove inizia la memoria che ci ha riservato. Il prototipo della funzione malloc è il seguente : void* malloc(size_t size); Ad esempio per allocare dinamicamente un array di 1000 caratteri : char *array; array = (char*)malloc(1000); Se la malloc fallisce (non riesce a riservare la memoria) il puntatore viene impostato a NULL. Questo ci permette di avere un minimo controllo e di dare eventualmente un messaggio di errore. Da questo punto in poi la memoria rimane a nostra disposizione finché non diciamo al sistema operativo che non ci serve più (o finché non usciamo dal programma). Una volta che la memoria ha terminato il suo compito per dire al sistema operativo che la memoria non ci serve più utilizzeremo la funzione free. Il prototipo della funzione free è: void free(void* block); 2
Per liberare la memoria quindi chiameremo : free(array); Uno degli utilizzi più comuni è quello ad esempio della allocazione dinamica di stringhe. Se ad esempio dobbiamo leggere da file una serie di righe di cui non conosciamo la lunghezza posso dichiarare un array monodimensionale di puntatori a carattere (char*) e allocare per ognuno di essi solo lo spazio che mi serve. Ad esempio : char* righe[1000];/* fisso un massimo di 1000 righe */ char templinea[1000]; /* array temporameo dove metto una sola riga */ int numerorighe = 0; /* apertura del file */.. while(! eof(fp)) /* finché non finisce il file */ { LeggiRiga(templinea); /* questa funzione legge una riga da file e la mette in templinea */ righe[numerorighe] = (char*)malloc(strlen(templinea) +1); /* c e anche il \0 */ strcpy(righe[numerorighe], templinea); numerorighe++; } Senza usare l allocazione dinamica saremmo stati costretti a dichiarare un array di caratteri da 1000 per 1000 (char linee[1000][1000];) che occupa la bellezza di 1'000'000 di bytes!!!! Con questa tecnica invece avremo occupato in memoria solo quello che effettivamente ci serve, con un incremento di prestazioni e una ottimizzazione della memoria occupata. Logicamente alla fine dell utilizzo dovremo deallocare tutta la memoria che abbiamo allocato. Per evitare problemi normalmente si tende ad inizializzare tutto l array di puntatori a NULL per poter riconoscere quali di questi puntatori andranno cancellati. for(i = 0; i < 1000; i++) righe[i] = NULL; e al termine del programma o dell utilizzo : for(i = 0; i < 1000; i++) if(righe[i]!= NULL) free(righe[i]); 3
Appendice B - Progettazione lineare di un programma Nella progettazione di un programma in C si procede nel mese seguente : Definizione del problema da risolvere Definizione degli input(dati necessari in ingresso), degli output (dati da mostrare in uscita) e dei dati necessari per il problema Scomposizione del problema in problemi più piccoli Analisi dei problemi più piccoli al fine di scomporli in problemi ancora più piccoli finché non si arriva ad usare le istruzioni e funzioni fondamentali del linguaggio. Considerando ad esempio il caso di un programma che debba fare la somma. Dati necessari in ingresso : le due cifre Dati da mostrare in uscita : risultato Operazioni da fare : Chiedere primo dato Chiedere secondo dato Effettuare la somma Mostrare il risultato A questo punto possiamo stendere un primo flusso del programma : int main() { int dato1; int dato2; int risultato; } dato1 = ChiediDato(); dato2 = ChiediDato(); risultato = Somma(dato1, dato2); Mostra(risultato); A questo punto ogni funzione che abbiamo creato andrà scomposta in funzioni più specifiche che a loro volta, come in una struttura ad albero, andranno scomposte fino al minimo livello possibile. Da notare che nella scomposizione del problema bisogna sempre cercare di mantenere un equilibrio nella grandezza e complessità delle funzioni. Nel nostro caso ad esempio sia per la funzione Somma, sia per la funzione Mostra si sarebbero potute usare le funzioni di sistema. Al contrario se si vuole mostrare in modo particolare (ad esempio all interno di una tabella) il risultato è necessario metterla in una funzione. Tenete conto che la grandezza e complessità va scelta in modo che non sia troppo dispersiva (perdo d occhio il flusso principale di operazioni) o troppo restrittiva (funzioni con pochissime istruzioni che avrebbero potuto essere comprensibili anche nel flusso del programma. Tenete inoltre conto che la semplicità è la chiave dell affidabilità. 4
Appendice C - Utilizzo dei file in modalità binaria Se necessario è possibile utilizzare, come abbiamo già visto la modalità binaria di accesso ai file. Con questa modalità abbiamo la possibilità di avere un controllo maggiore sul file. Possiamo infatti muoverci avanti e indietro all interno del file e leggere o scrivere intere parti di file. La funzioni più comuni nell utilizzo della modalità binaria sono fread, fwrite, fseek e ftell. Con la funzione fread ci permette di leggere una parte del file e di metterle in una certa zona di memoria. Il prototipo di questa funzione è : size_t fread(void *ptr, size_t size, size_t n, FILE *stream); I parametri di questa funzioni lavorano nel modo seguente: ptr è il puntatore all inizio della zona di memoria nella quale vogliamo mettere i nostri dati, size è la lunghezza di ogni elemento che vogliamo cariare in memoria, n è il numero di elementi da caricare e stream è il FILE pointer da cui dobbiamo leggere i dati. La funzione torna il numero di oggetti (non bytes) letti. Questo ci permette di controllare che tutti i dati che volevo leggere siano stati letti. Il fatto di usare i due parametri dimensione del dato e numero di dati è utile se pensiamo ai dati come a dei record di un database. Il compilatore in realtà si limiterà a leggere size*n bytes e a memorizzarli a partire dal puntatore ptr, ma ci toglie dall incombenza di calcolare noi la memoria e rimane molto più leggibile. Facciamo degli esempi : char mem[1000];... fread(mem, sizeof(mem[0]), 1000, fp); Leggerà 1000 bytes (il record è grande come un char) dal file. Con programmi più complessi, che utilizzino ad esempio strutture, la modalità binaria ci permette di leggere record per record (una struttura intera) o leggere tutti i record che dobbiamo leggere. Ad esempio struct persona { char nome[100]; char cognome[100]; };... struct persona impiegati[1000]; fread(impiegati, sizeof(struct persona), 1000, fp); Con questa fread abbiamo letto con una sola istruzione tutti i 1000 record della struttura impiegati e li abbiamo messi in memoria. La funzione fwrite è la complementare della precedente. Ha infatti gli stessi parametri e si comporta nello stesso modo a parte il fatto che scrive sul file invece che leggere dal file. Il vantaggio che ci dà la modalità binaria è inoltre quello del maggiore controllo che abbiamo sul file. Ci possiamo infatti muovere avanti e indietro all interno del file. Pensate al file binario come ad un nastro (in effetti la modalità deriva proprio da questo). Abbiamo quindi un puntatore che ci indica dove siamo all interno del file. 5
La funzione che serve per cambiare posizione nel file è la fseek che ha il seguente prototipo : int fseek(file *stream, long offset, int whence); Il parametro stream è il file pointer, offset è lo spostamento all interno del file dal rifermento che decidiamo e whence è il nostro riferimento che può valere : SEEK_SET Il riferimento è l inizio del file SEEK_CUR Il riferimento è la posizione corrente nel file SEEK_END Il riferimento è la fine del file Nell esempio precedente se invece di leggere tutto il file avessimo voluto leggere solo il record numero 155 sarebbe bastato scrivere : fseek(fp, sizeof(struct persona) * 155, SEEK_SET); fread(impiegati, sizeof(struct persona), 1, fp); Per conoscere invece la posizione corrente del file pointer si utilizza la funzione ftell. Il suo prototipo è : long ftell(file *stream); Viene passato alla funzione il file pointer e questa mi torna un valore maggiore o uguale a 0 se non ha errori o -1L se ne ha. Usando in combinazione queste funzioni è possibile lavorare sui vari record di un file senza doverlo necessariamente leggere tutto in memoria. Per conoscere ad esempio il numero di record contenuti nel file dell esempio precedente basta fare : long numero_record; fseek(fp, 0, SEEK_END); /* si posiziona alla fine del file */ numero_record = ftell(fp) / sizeof(struct persona); A questo punto potete usare l allocazione dinamica della memoria per allocare solo lo spazio in memoria che vi serve con notevole risparmio di risorse. 6
Appendice D - Utilizzo del programma make Per rendere più leggibile e maneggevole un programma in C si tende a dividere lo stesso in più file sorgenti che andranno poi uniti insieme. Molti ambienti (come il Borland o il microsoft) hanno un gestore delle dipendenze dei file integrato, ma il modo più comune, soprattutto se pensate di compilare lo stesso programma su compilatori o sistemi operativi diversi è appunto quello di usare il programma make. Il make ci permette di definire delle variabili, di definire dei target, cioè degli obbiettivi per quel tipo di esecuzione del make. Con il target possiamo ad esempio fare una parte del make che ripulisce tutti i file oggetto del progetto, o un target che compila tutto il progetto con le funzioni di debug, o ancora con opzioni diverse se volessimo differenziare il programma. Vedremo più avanti con degli esempi. Un altro strumento che il make ci dà a disposizione sono le variabili. Una variabile viene definita con il formato : VARIABILE = valore_variabile Per ricavarne il valore si usa invece : $(VARIABILE). Sicccome il make espande le variabili negli assegnamenti non è possibile fare : $(VARIABILE) = $(VARIABILE) -o perché il valore di $ variabile sarebbe accodato in un loop infinito. Per accodare un testo ad una variabile è necessario invece usare la sintassi : CC := gcc CC += -o per l assegnamento, e per l append di altri valori. Possiamo quindi iniziare a scrivere il nostro makefile. Se chiamate make senza parametri infatti viene usato il file makefile. Poniamo il caso che abbiamo un progetto il quale sia composto da tre file : main.c files.c utility.c -> dipendente da files.h -> dipendente da utility.h Il file inizierà con : # Inizio del makefile # Variabili definite dal programmatore Nome del compilatore CC = gcc VERSION = Versione 1.0 file oggetto da unire nel file finale OBJS = main.o files.o utility.o A questo punto possiamo iniziare a definire i vari target. Il primo che possiamo vedere è quello per pulire il progetto da tutti gli objs. Questa parte andrà in realtà al termine del file, perché il make chiamato senza parametri esegue il primo target del progetto clean: rm *.o per unix 7
del *.obj per dos Chiamando make clean tutti i file.o o.obj saranno cancellati dalla directory del makefile. Questo può essere utile se facciamo due target differenti per fare un programma da debuggare (in sviluppo) o un programma definitivo (senza debug e più snello). Cominciamo il secondo target per compilare normalmente un programma. Poniamo che il programma si debba chiamare prog01. prog01: $(OBJS) $(CC) o prog01 $(OBJS) Questa parte unisce i file oggetto che abbiamo specificato nel file eseguibile. Definiamo quindi e regole per creare i file oggetto : main.o : main.c $(CC) c main.c files.o : files.c files.h $(CC) c files.c utility.o : utility.c utility.h $(CC) c utility.c Make, per ogni file oggetto andrà a controllare lo stato di dipendenza (andando a prendere il target ad esso associato) del file oggetto dai file.c e.h ad esso correlati per ognuno di essi lancerà la linea di comando corrispondente. Una volta terminate tutte de dipendenze dei file oggetto andrà a chiamare la linea del file programma (prog01) da cui era partito ed eseguirà la linea corrispondente. In pratica per ogni modulo che aggiungete al progetto va aggiunto il file oggetto nella variabile OBJS e va aggiunta la sezione corrispondente con le dipendenze nel file aggiunto. Possiamo inoltre mettere (in unix/linux in realtà è proprio un modo di lavorare) un target con i comandi per l installazione. install: mkdir./mioprogramma1.0 mv./prog01./mioprogamma1.0 echo $(VERSION) > leggimi.txt mv./leggimi.txt./mioprogamma1.0 con questi pochi comandi (che funzioneranno solo per unix/linux) creiamo una directory dove andremo a mettere il programma. Per dos/windows dovremo usare logicamente i comandi batch corrispondenti. Il make è un programma presente in tutte le installazioni di compilatori C o C++. In realtà non è stato fatto esplicitamente per il C. E un programma che riesce ad eseguire automaticamente, come avrete già capito, una serie di operazioni ed ha una flessibilità straordinaria. L utilizzo di questo programma vi permette di portare un progetto da un sistema ad un altro con poche modifiche, mentre usando un ambiente integrato, seppur più comodo, non abbiamo questa possibilità. Tenete conto che abbiamo visto solo una piccolissima parte delle sue potenzialità. 8
Appendice E - Tabella codici ASCII Standard (0 127) HEX DEC CHR CTRL HEX DEC CHR HEX DEC CHR HEX DEC CHR 00 0 NUL ^@ 20 32 SP 40 64 @ 60 96 ` 01 1 SOH ^A 21 33! 41 65 A 61 97 a 02 2 STX ^B 22 34 " 42 66 B 62 98 b 03 3 ETX ^C 23 35 # 43 67 C 63 99 c 04 4 EOT ^D 24 36 $ 44 68 D 64 100 d 05 5 ENQ ^E 25 37 % 45 69 E 65 101 e 06 6 ACK ^F 26 38 & 46 70 F 66 102 f 07 7 BEL ^G 27 39 ' 47 71 G 67 103 g 08 8 BS ^H 28 40 ( 48 72 H 68 104 h 09 9 HT ^I 29 41 ) 49 73 I 69 105 i 0A 10 LF ^J 2A 42 * 4A 74 J 6A 106 j 0B 11 VT ^K 2B 43 + 4B 75 K 6B 107 k 0C 12 FF ^L 2C 44, 4C 76 L 6C 108 l 0D 13 CR ^M 2D 45 4D 77 M 6D 109 m 0E 14 SO ^N 2E 46. 4E 78 N 6E 100 n 0F 15 SI ^O 2F 47 / 4F 79 O 6F 111 o 10 16 DLE ^P 30 48 0 50 80 P 70 112 p 11 17 DC1 ^Q 31 49 1 51 81 Q 71 113 q 12 18 DC2 ^R 32 50 2 52 82 R 72 114 r 13 19 DC3 ^S 33 51 3 53 83 S 73 115 s 14 20 DC4 ^T 34 52 4 54 84 T 74 116 t 15 21 NAK ^U 35 53 5 55 85 U 75 117 u 16 22 SYN ^V 36 54 6 56 86 V 76 118 v 17 23 ETB ^W 37 55 7 57 87 W 77 119 w 18 24 CAN ^X 38 56 8 58 88 X 78 120 x 19 25 EM ^Y 39 57 9 59 89 Y 79 121 y 1A 26 SUB ^Z 3A 58 : 5A 90 Z 7A 122 z 1B 27 ESC 3B 59 ; 5B 91 [ 7B 123 { 1C 28 FS 3C 60 < 5C 92 \ 7C 124 1D 29 GS 3D 61 = 5D 93 ] 7D 125 } 1E 30 RS 3E 62 > 5E 94 ^ 7E 126 ~ 1F 31 US 3F 63? 5F 95 _ 7F 127 DEL 9
Appendice F Tipi più comuni nella generazione delle CGI Type of file Extension MIME Type HTML.html,.htm text/html Text only (no markup).txt,.text text/plain Formaldehyde (formerly NetCloak).nclk text/html Formaldehyde (formerly NetForms).fdml text/html Formation (formerly Flexmail).flx,.fcn text/html Tango.qry text/html Java Class.class application/octet-stream JavaScript.js application/x-javascript GIF image.gif image/gif JPEG image.jpg,.jpeg image/jpeg MIDI files.midi,.mid audio/x-midi Quicktime movie.mov,.qt,.qtvr video/quicktime Mpeg movie.mpg,.mpeg vido/mpeg Director/Shockwave.dir application/x-director Director/Shockwave.dcr application/x-director Director/Shockwave.dxr application/x-director Flash.swf application/x-shockwave-flash AIFF sound file.aiff audio/x-aiff AU sound file.au audio/baisc RealAudio.ra,.rm,.ram,.rpm application/x-pn-realaudio Wav sound file.wav application/x-wav Acrobat (PDF) format.pdf PostScript.ps application/postscript RTF (Rich Text Format).rtf application/rtf Word document.word,.doc application/msword ClarisWorks.works (recommended) Powerpoint.ppz application/ppt Excel document.xl,.xls,.xlt application/excel Lotus 1-2-3.123 application/lotus Binhex files.hqx application/mac-binhex40 GZ files.gz application/x-gzip MacBinary.bin application/x-macbinary StuffIt files.sit application/x-stuffit Tar files.tar application/x-tar Zip files.zip application/zip PC Executable.exe application/octet-stream 10