Appendice A Lo stile Ungherese L'insieme di regole dello stile ungherese [SH91] assegna i nomi di variabile come composizione di un tipo e di un qualicatore e quelli di funzione come composizione (opzionale) di tipo, azione(i), parametri. Cos ad esempio cpFirst sta ad indicare un char pointer al primo elemento di un qualche intervallo di valori. Tipi complessi vengono generati tramite composizione di tipi elementari e cos funzioni o variabili complesse. Il problema con questo approccio, che tra l'altro consente la creazione di un'algebra dei tipi, e in qualche modo la sua poca leggibilita, se non a spese di una eccessiva prolissita nell'uso degli acronimi. Certo, una volta deniti i tipi e utilizzando dei qualicatori standard o in ogni caso autoesplicativi, si ha a disposizione uno strumento molto potente, ma che continua a richiedere una attenzione che non sempre un programmatore non professionista possiede e non risulta comunque semplice leggere il codice se non previa scansione della lista dei signicati associati agli acronimi dei tipi. 28 Bibliograa [Ame89] American National Standard for Information Systems. Programming language { C. Technical Report X3.159, ANSI Accredited Standards Committee, X3 Information Processing Systems, 1989. [CEK+ 95] L.W. Cannon, R.A. Elliott, L.W. Kirchho, J.H. Miller, J.M. Milner, R.W. Mitze, E.P. Schan, N.O. Whittington, H. Spencer, D. Keppel, and M. Brader. Recommended C style and coding standards. Technical report, April 1995. update version of the Indian Hill C Style and Coding Standars, ftp://archive.cis.ohio-state.edu/pub/style-guide. [Cof90] S. Con. The UNIX System V Release 4 - The Complete Reference. McGraw-Hill, 1990. [DLK95] A. Dolenc, A. Lemmke, and D. Keppel. Notes on writing portable programs in C, 1995. ftp://archive.cis.ohio-state.edu/pub/style-guide. [ESA91] ESA. C style. Technical report, Expert Solutions Australia Pty. Ltd., 1991. ftp://archive.cis.ohio-state.edu/pub/style-guide. [Fre] Free Software Foundation, Cambridge, MA. ftp://prep.ai.mit.edu/pub/gnu/. GNU Emacs Manual. [GFM+ 94] C. Ghezzi, A. Fuggetta, S. Morasca, A. Morzenti, and M. Pezze. Ingegneria del Software. Mondadori Informatica, 1994. [GJM91] C. Ghezzi, M. Jazayeri, and D. Mandrioli. Fundamentals of Software Engineering. Prentice-Hall, 1991. [Hor90] M. Horton. Portable C Software. Prentice-Hall, 1990. [KR88] B. W. Kernighan and D. M. Ritchie. The C Programming Language. Prentice-Hall, 1988. Second Edition. [Lam86] L. Lamport. LaTeX, A document preparation system. Addison-Wesley, 1986. [MAI93] Progetto MAIA. Standard di programmazione per il linguaggio C. Technical report, Istituto per la Ricerca Scientica e Tecnologica (IRST), July 1993. TE 93|22. [Moy] P. J. Moylan. The case against C. Technical report, Department of Electrical and Computer Engineering, University of Newcastle. [email protected]. [SH91] C. Simonyi and M. Heller. The hungarian revolution. Byte, (16):131{138, Agust 1991. 27 Ringraziamenti Questo documento e stato prodotto nell'ambito del Progetto Speciale 1995: Algoritmi e software per ottimizzazione, e modelli di sistemi complessi del Dipartimento di Matematica dell'Universita degli Studi di Trento. 26 2.3 La documentazione Fornire una dettagliata documentazione che descriva gli obiettivi del proprio progetto software e metta a disposizione ogni informazione necessaria all'utilizzo di un applicativo o di una libreria, rappresenta una componente importante, spesso a torto sottovalutata: ma di quale utilita e un qualsiasi software se non lo si sa utilizzare correttamente e sfruttarne al meglio le potenzialita ? E quindi necessario sempre includere nella directory src un le README che chiarisca gli obiettivi e le caratteristiche del prodotto sviluppato, riassumendo, eventualmente, la lista delle funzioni e/o delle funzionalita, corredate da una opportuna descrizione. Nel caso di applicativi di una certa importanza, puo essere utile creare dei documenti (e fortemente consigliato l'uso del latex [Lam86]) in formato .tex o .ps o .man, da inserire nella directory doc. Non si vogliono dare delle speciche rigide riguardo alla documentazione, pero potrebbe essere buona norma creare un le README contenente alcuni campi descrittivi standard che specichino: autore data versione organizzazione architetture sulle quali e stato testato problemi noti nalita funzionalita una qualche lista descrittiva delle componenti funzionali. 25 file: Makefile # paths for ROOT_DIR= SRCDIR= INCDIR= LIBDIR= OBJDIR= DOCDIR= directories /usr/spiderman/MyWeb $(ROOT_DIR)/src/$(SRC) $(ROOT_DIR)/inc/$(SRC) $(ROOT_DIR)/lib/$(ARCHITECTURE)/$(SRC) $(ROOT_DIR)/obj/$(ARCHITECTURE)/$(SRC) $(ROOT_DIR)/doc/$(SRC) # source and target files SRC= TabuSearch OBJ= $(OBJDIR)/$(SRC).o LIB= $(LIBDIR)/$(SRC).a # compilation options CC= gcc -I$(INCDIR) CFLAGS= -Wall -O2 LIBRARIES= LDIR= LINK= -limage -ldsp /usr/local/lib $(CC) -L$(LDIR) $(LIBRARIES) AR= ar rc # main target all: dir $(OBJ) $(LIB) @echo # create the sub-directories, move the header files in the $(INCDIR) and # create symbolic links for them in $(SRCDIR) dir: mkdir $(LIBDIR) mkdir $(OBJDIR) mkdir $(INCDIR) mkdir $(DOCDIR) mv $(SRC).h $(SRC)P.h $(INCDIR) ln -s $(INCDIR)/$(SRC).h . ln -s $(INCDIR)/$(SRC)P.h . # make the obj $(OBJ): $(SRC).c $(SRC).h $(SRC)P.h @echo @echo 'Making $(SRC).o ...' $(CC) -o $(OBJDIR)/$(SRC).o -c $(CFLAGS) $(SRC).c @echo '... done !' @echo # make the lib $(LIB): $(OBJ) @echo @echo 'Making $(LIB) ...' $(AR) $(LIB) $(OBJ) @echo 'Installing $(LIB) ...' ranlib $(LIB) @echo '... done !' @echo 24 src: TabuSearch: TabuSearch.c README Makefile inc: TabuSearch: TabuSearchP.h TabuSearch.h lib: SUNOS: TabuSearch: TabuSearch.a HPPA: TabuSearch: TabuSearch.a obj: SUNOS: TabuSearch: TabuSearch.o HPPA: TabuSearch: TabuSearch.o doc: TabuSearch: TabuSearch.ps TabuSearch.1 Per utilizzare al meglio questa struttura dei le e e opportuno utilizzare un modello standard di Makele, che consenta di velocizzare e semplicare le operazioni di compilazione. Ne viene qui proposto uno estremamente semplice, che puo essere utile come modello di partenza. Si consideri il caso dell'esempio precedente, con i le .c e .h inizialmente nella sotto-directory TabuSearch di src e con le directory lib, obj, inc, doc precedentemente create; il makele e strutturato in modo tale che con make dir si creano le directory e con make all si eettuano le operazioni di compilazione ed archiviazione della libreria (i comandi di shell sono quelli tipici di un ambiente Unix): 23 2.2 La struttura delle directory e la compilazione La proposta e quella di razionalizzare la localizzazione dei le all'interno del proprio le system per consentire una rapida individuazione dei sorgenti, delle librerie, degli include e degli oggetti e standardizzare in questo modo anche le procedure di compilazione, oltre che semplicare le inclusioni, i linkaggi e le compilazioni per architetture dierenti. Un metodo razionale ed ampiamente diuso e quello adottato nei sistemi Unix[Cof90], che prenderemo come riferimento. L'idea di base e quella di introdurre in una opportuna directory radice le seguenti sottodirectory: bin - per gli eseguibili degli applicativi lib - per le librerie realizzate con le funzioni sviluppate src - per i le sorgente .c inc - per gli header le .h obj - per i .o prodotti durante le fasi di compilazione tmp - per eventuali le temporanei doc - per una eventuale documentazione annessa agli applicativi. Ognuna di queste directory risultera a sua volta organizzata in sottodirectory, una per ogni programma applicativo sviluppato, in modo da non creare sovrapposizioni ed interferenze, mantenendo il tutto logicamente separato (si osservi che tale operazione sara realizzata tramite un makele standard a partire dalle sotto-directory src). Con questa organizzazione risultano immediate ricerche di applicativi, sorgenti, funzioni, documentazioni, librerie: si sa esattamente dove e situato e con quale criterio e organizzato il software prodotto. In molti casi fortunati, si hanno piu macchine a disposizione ed un le system condiviso tra loro, per sara necessario produrre le binari per macchine dierenti: in questo caso le directory bin, lib, obj saranno organizzate a loro volta in sottodirectory, una per ogni diversa architettura per cui risulta possibile compilare. Ad esempio, se l'insieme di funzioni raggruppate nel modulo di nome TabuSearch e stato compilato per il sistema operativo SunOS e per l'HP-UX, allora le directory saranno strutturate nel modo seguente: 22 Gli header avranno invece la seguente forma: file: file: module_1P.h /* comments fields here... module_1.h /* comments fields here... */ */ /* multiple inclusions flag */ #ifndef MODULE_1P_H #define MODULE_1P_H /* multiple inclusions flag */ #ifndef MODULE_1_H #define MODULE_1_H /* includes for local functions */ #include <stdlib.h> #include <stdio.h> /* includes needed to use exported functions */ #include <time.h> #include "module_2.h" #include "module_3.h" #include "module_4.h" /* exported simbolic constants */ #define ERROR_CODE 2 /* local constants */ #define THIS_IS_A_LOCAL_CONST 100 /* exported macros */ #define Eval(x) ((x)+2) /* local macros */ #define abs(x) (((x)>0?(x):-(x)) /* local types */ typedef unsigned long /* exported types */ typedef char Byte time_t; /* exported global variables */ extern int ExportedVar; /* private global variables */ static int GlobalVar; /* exported functions prototypes */ extern void ExportedFunction(int arg); #endif /* exported global variables (see exported header) */ int ExportedVar; /* local functions prototypes */ static void local_function(int arg); #endif E opportuno fare alcune osservazioni: no a questo momento sono state suggerite regole orientate alla modularita e all'incapsulamento dell'informazione, ma e doveroso far notare che il programmatore deve portare estrema attenzione nell'implementazione, nel controllare tutti i dettagli, perche il compilatore C non fornisce un valido supporto in questo senso, non vengono fatti dei controlli, ad esempio sull'uso degli static o degli extern, che tutelino il programmatore da errori. L'idea e che in C si puo scrivere secondo i principi della programmazione modulare ma il compilatore non forza il programmatore a farlo e non e in grado di eettuare dei controlli ecaci (si veda ad esempio [Moy] per un dettagliata discussione critica riguardo l'utilizzo del C). 21 file: module_1.c /* comments fields here... */ #include "application_global_header.h" #include "module_1.h" #include "module_1P.h" /* groups of functions exported to other modules */ void ExportedFunction(int arg) { ... } ... /* groups of functions local to this module */ static void { ... } local_function(int arg) ... 20 lista dei prototipi di tutte le funzioni di tipo extern provenienti da altri moduli; insieme di tutte le costanti private, denite tramite #define, enum, const; insieme di tutte le macro private; insieme dei tipi privati; insieme delle variabili globali ma private, dichiarate esplicitamente come static; insieme delle variabili globali esportate. Questa sezione duplica quella presente nell'header pubblico, rimuovendo l'extern per consentire di vedere tali variabili come appartenenti al modulo in questione; insieme dei prototipi delle funzioni usate solo all'interno del le sorgente e dichiarate static; header globale (eventuale): contiene tutte quelle risorse denite a livello dell'applicazione stessa, in qualche modo utili o necessarie a tutto l'insieme dei moduli. Esso si articola, in analogia con i due header precedenti, nelle parti seguenti: ag di preprocessore per evitare inclusioni multiple; lista di le di #include. insieme di costanti simboliche ; insieme di macro; insieme di tipi; insieme di variabili globali: sezione da mantenere assolutamente vuota; le sorgente (.c): rappresenta il le contenente le funzioni che codicano i servizi oerti dal modulo. Risulta strutturato nel seguente modo: una sezione di #include per l'inclusione dell'eventuale header globale; l'header pubblico; l'header privato; uno o piu gruppi di funzioni esportate; uno o piu gruppi di funzioni locali, dichiarate static; Un esempio completo aiutera a chiarire le idee: si sia realizzato un progetto software per la realizzazione di un programma applicativo chiamato new application, e l'insieme delle funzioni sia stato logicamente suddiviso in piu moduli raggruppanti insiemi di funzioni tra loro omogenee. Il le sorgente dell'i-esimo modulo abbia nome module i.c, per cui il suo header privato si chiamera module iP.h e quello pubblico module i.h. Per meglio illustrare la situazione si considerera il caso in cui si siano esportate variabili globali. Il le sorgente, ad esempio del primo modulo, avra la forma: 19 word extern alle variabili globali esportate nell'header pubblico e di replicare le denizioni, ma senza l'extern, nell'header privato. Considerando che l'idea fondamentale e quella di evitare l'uso di variabili esportate, la complicazione aggiuntiva cos introdotta e minima (si veda ad esempio [MAI93] per una soluzione alternativa piu articolata). La struttura del le sara dunque la seguente, con intercalate linee di commento separanti le varie sezioni: ag di preprocessore per evitare inclusioni multiple: la convezione, largamente accettata e quella di introdurre all'inizio del le le seguenti istruzioni di preprocessore: HeaderFileName H #define HeaderFileName H #ifndef ed alla ne del le l'endif conclusivo del costrutto condizionale: #endif /* #ifdef HeaderFileName H */ lista di tutti i le di #include di sistema e non, necessari per l'utilizzo delle entita esportate. Si osservi che non sarebbe meglio evitare di avere inclusioni innestate, ma, se ogni header le contiene il ag per evitare le inclusioni multiple, come ormai e prassi nei moderni compilatori, allora non esistono sostanziali controindicazioni, ma e comunque necessario ricordare che nell'eventuale makele vanno considerate le inclusioni innestate per cio che concerne le dipendenze; insieme di tutte le costanti simboliche esportate all'esterno, denite tramite #define, enum, const; insieme di tutte le macro esportate; insieme dei tipi resi pubblici; insieme delle variabili globali rese accessibili anche a funzioni di altri moduli: come gia fatto osservare, e necessario preporre la keyword extern ad ogni variabile denita. insieme dei prototipi delle funzioni rese pubbliche e quindi utilizzabili da moduli esterni. Valgono considerazioni analoghe a quelle fatte per le variabili globali, per cui ad ogni prototipo verra anteposta la keyword extern. header privato: l'idea e quella di separare tutto cio che e strettamente locale alle funzioni del le sorgente da quello che invece viene anche esportato e reso disponibile ad altri moduli. Per cui in questo header, il cui nome e costituito dal nome del sorgente a cui fa riferimento con una \P" postssa (es. appl.c, applP.h), troveranno posto tutte le quantita di uso interno alle funzioni del sorgente. Il le risultera articolato nelle seguenti sezioni, separate da opportune linee di commento: ag di preprocessore per evitare inclusioni multiple, analogo a quello descritto per l'header pubblico; lista di tutti i le di #include necessari per le funzioni, le variabili ed i tipi locali; 18 Capitolo 2 L'ambiente Con la parola ambiente si intende l'organizzazione e la localizzazione nel le system dell'insieme dei le sorgente e di inclusione che costituiscono i moduli software sviluppati per realizzare un determinato applicativo o per creare o aggiornare una libreria, unitamente ai le di documentazione e di gestione della compilazione che ad essi sono allegati. Ci si riferisce correntemente a queste problematiche utilizzando il termine di programmazione in-the-large. 2.1 I le sorgente L'unita di base di un progetto software [MAI93] deve essere il modulo, costituito da: un le sorgente (.c) e da due header le (.h), uno privato e l'altro pubblico. Un progetto, nella sua interezza, risultera composto da un insieme di moduli tra loro linkati e da un eventuale header le globale. La struttura ed il contenuto dei le di ciascun modulo, organizzate in modo da facilitare la modularita e razionalizzare la dislocazione delle singole strutture informative, risultano essere essere le seguenti (non verrano ribadite le componenti relative ai commenti, descritte nel par. 1.1.4): header pubblico: in questo le, che avra lo stesso nome del sorgente con ovviamente il susso .h al posto del .c, vengono poste tutte le dichiarazioni di tutti gli oggetti resi disponibili all'esterno: questo header sara incluso da altri moduli per avere accesso ai \servizi" forniti dal modulo a cui esso si riferisce. Ci saranno, quindi, i prototipi delle funzioni esportate, i tipi e le costanti necessarie per il loro utilizzo (\l'interfaccia" alle funzioni), e tutto un insieme di costanti, variabili globali, macro, tipi, utilizzati dalle funzioni del le sorgente e di utilita anche per altri moduli. La componente principale e quella che e stata chiamata interfaccia, che deve risultare perfettamente consistente: una volta che l'header pubblico in questione viene incluso all'interno di un le sorgente di un dierente gruppo di funzioni, all'atto dell'utilizzo dell'insieme esterno di funzioni non dovranno sorgere inconsistenze derivanti da denizioni o macro mancanti. Come discusso nel par. 1.2, sarebbe opportuno evitare di esportare variabili globali all'esterno di un modulo, ma in alcuni casi cio puo risultare comodo ed eciente. In questo caso sorge il problema di dover vedere come extern le variabili globali quando queste sono importate da altri moduli. La convenzione qui adottata per aggirare il problema e quella di preporre la key17 void *safe_malloc(int bytes, char *msg) { void *ptr; } if ( (ptr = malloc(bytes)) == NULL ) { fprintf(stderr, "%s", msg); exit(1); } return(ptr); void any(void) { float *f; } f = safe_malloc(VEC_DIM * sizeof(float), ``Not enough space for array f\n''); ... uso dei tipi standard: bisogna utilizzare i tipi standard di dato in modo appropriato, avendo ad esempio cura di dichiarare una variabile unsigned solo se si e sicuri della sua non negativita oppure evitando di utilizzare i char per individuare interi con un range limitato di valori. In generale quando vengono eseguiti calcoli dipendenti dalla lunghezza in bit del tipo di dato, non bisogna mai fare assunzioni predenite (cio vale anche per i puntatori), ma bisogna utilizzare sempre la funzione sizeof() che consente una ampia portabilita del codice; uso della memoria: non fare in nessun modo assunzioni su allineamenti di dati o su contiguita di strutture o array per fare pericolosi calcoli con puntatori: la struttura degli elaboratori e le modalita di gestione della memoria non consentono di scrivere codice minimamente portabile o sicuro se si fanno assunzioni sulle strategie di allocazione sica. 16 funzioni: tutte le funzioni debbono avere il proprio prototipo di denizione da inserire in un opportuno header le secondo le direttive che verrano illustrate nel par. 2.1. Il prototipo consente il type checking da parte del compilatore e, esistendo d'altra parte numerosi pacchetti software che estraggono i prototipi automaticamente, risulta un'operazione a costo nullo. Le funzioni dovrebbero essere scritte in modo che sia non ambigua la loro appartenenza alla classe delle funzioni vere e proprie, cioe quelle che non modicano i propri argomenti ma ritornano solo un valore derivante da una certa valutazione del loro stato, oppure appartengono alla classe delle procedure, le quali a loro volta possono essere suddivise in procedure semplici, eettuanti una sequenza di operazioni individuanti una azione logicamente atomica (ad esempio la lettura dei dati di un le e l'immagazzinamento degli stessi in un array), ed in procedure complesse, all'interno delle quali molteplici azioni dierenziate non logicamente atomicamente raggruppabili vengono eettuate (ad esempio, nella stessa procedura si puo leggere un le, ricevere un input da tastiera ed inviare dati ad una stampante). E chiaro che le funzioni vere e proprie e le procedure semplici risultano maggiormente riutilizzabili, di piu semplice scrittura e comprensione, e favoriscono una scrittura modulare del codice, in quanto incapsulano atomicamente strutture dati, concetti ed azioni, mentre le funzioni complesse, in genere, sono utilizzabili solo per il compito specico per cui sono state sviluppate. Per passare un numero variabile di argomenti, lo standard ANSI [Ame89] ha introdotto l'interfaccia stdarg, che diventa quindi l'unico standard accettabile. Esso risulta costituito da un insieme di macro denizioni, va list, va start, va end, situate nel le stdarg.h. Si osservi che esiste un ulteriore insieme quasi equivalente di macro, varargs, largamente diuso ma reso obsoleto dal nuovo standard. Nell'interfaccia stdarg e il valore del primo argomento, di tipo ssato, che consente la corretta scansione della successiva lista di argomenti. Ad esempio, nella funzione printf, il cui prototipo sara: int printf(char *format, ...) (dove i ... indicano la lista variabile), sono i campi della stringa di formato, specicati dal valore della variabile format, che permettono di leggere correttamente i successivi argomenti. A partire dal puntatore all'area di memoria contenente gli argomenti della funzione, viene letto il preciso numero di byte associato alla lunghezza del tipo della variabile. Se ci si trova nelle condizioni di dover utilizzare ambienti strutturati a thread le funzioni dovrebbero essere rese rientranti, ovvero non dipendenti da variabili globali, prive di dati locali statici e con gli argomenti generalmente passati attraverso un puntatore ad una struttura; funzioni di utilita: spesso il codice e appesantito da tutti i controlli che vengono eettuati sui valori di ritorno dalle funzioni per controllare la stato di riuscita dell'azione. Per alcune azioni standard, come l'apertura di un le o l'allocazione dinamica, puo risultare conveniente scrivere delle semplici routine per interrompere l'esecuzione in caso di errore, ad esempio: 15 variabili globali, static, extern: dati globali a piu moduli software vanno assolutamente evitati, in quanto non c'e alcuna protezione rispetto ad un qualsiasi mutamento dei valori. L'idea e che, in un programma, ogni tipo di dato dovrebbe appartenere a un \qualcosa" che ne ha l'esclusivo onere della gestione. Il modo migliore per realizzare cio e quello di fornire una funzione di accesso ad esso, ovvero una interfaccia che ne nasconde i dettagli e ne garantisce la protezione contro cambiamenti non controllati: /* this is a wrong mode */ extern ColorType ColorsTable[]; /* global variable */ circle_draw(x, y, ray, ColorsTable[color]); /* this is a correct mode */ extern ColorType ColorsTable(int color); /* global function */ circle_draw(x, y, ray, ColorsTable(color)); nell'esempio, se il modo di estrarre il colore dalla tavola dei colori cambia per qualche ragione, nel primo caso debbono essere riviste tutte le istanze di ColorsTable[color] in tutti i le, mentre nel secondo caso l'interfaccia resta invariata ed il cambiamento verra apportato solo all'interno della funzione ColorsTable(). Questo modo di trattare variabili logicamente globali dovrebbe essere applicato anche a variabili globali statiche, ovvero non esportate, proprio in virtu del maggiore controllo e essibilita che questo approccio (information hiding) determina. Tutti i dati globali ma non esportati e le funzioni locali ad un le vanno rigorosamente dichiarati come static mentre tutto cio che si esporta va indicato come extern, in questo modo si evitano inclusioni indesiderate e risulta immediatamente chiaro il ruolo delle varie componenti; accesso a strutture dati: l'accesso a strutture dati esportate e/o di fondamentale interesse per il programma va fatto attraverso funzioni e/o macro la cui semantica sia immediatamente chiara dal nome. In questo modo si nasconde il preciso nome dei campi e si associa ad essi invece un signicato piu intuitivo. Questa tecnica puo essere utilizzata anche per creare e gestire strutture dati in modo implicito: struct RectangleType int int rectangle; GetRectangleWidth(struct RectangleType SetRectangleWidth(struct RectangleType *rectangle); *rectangle, int width); /* manage a rectangle internally through a descriptor */ typedef int RectangleDescriptor; RectangleDescriptor create_rectangle(int width, int height); int get_rectangle_width(RectangleDescriptor desc); 14 devoluta al fatto che il #define ha valore globale e non e possibile circoscriverne il raggio d'azione. L'uso del prepocessore va incoraggiato nel caso della compilazione condizionale di codice in funzione del tipo di macchina e/o di compilatore, in quanto garantisce in modo estremamente semplice una ampia piattaforma di portabilita al proprio codice: #if defined(SUNOS) || defined(SGI) #include <gnat.h> #define MAX_LEN 1000 #else #include <gnatgnat.h> #define MAX_LEN 2000 #endif #if defined(__STDC__) void fun(int arg); #else void fun(/* int arg */); #endif Ovviamente la medesima tecnica puo essere utilizzata per discriminare in funzione di un qualche ag, anche se in linea generale e sempre meglio fornire a tempo di esecuzione i ag condizionali non legati all'architettura o a variabili di sistema, e quindi sostituire gli #if con degli if, perche, anche se cio determina una piccola diminuzione di ecienza, aumenta la essibilita del programma. Molta attenzione deve essere devoluta nell'utilizzare il preprocessore per denire delle macro: vanno assolutamente evitate le denizioni di tipi, che debbono essere realizzate con il typedef per evitare eetti indesiderati e consentire il type-checking da parte del compilatore: #define CharPtr char * CharPtr a, b; /* but now b is a char not a char * !!! */ Se invece si vogliono denire delle funzioni come macro, allora (si veda la denizione nel par. 1.2), le macro possono essere utilizzate solo per implementare funzioni C che siano esattamente funzioni, ed avendo cura di proteggere sempre con delle parentesi gli argomenti per evitare eetti indesiderati: #define abs(x) x>0?x:-x /* abs(a-b) is expanded as: a-b>0?a-b:-a-b ...that is wrong !!! */ #define correct_abs(x) (((x)>0?(x):-(x)) 13 1.2 Aspetti sintattici In questa sezione si cercara di fornire un insieme di regole atte a migliorare le caratteristiche di correttezza, portabilita ed ecienza di un programma C [KR88, Ame89]. L'idea non e quella di fornire in modo dettagliato una lista di avvertimenti sulle insidie piu o meno nascoste degli operatori e dei costrutti o dare suggerimenti su come ottimizzare il proprio codice, in quanto ogni buon libro sul C e in grado di fornire precise informazioni su questi argomenti. Alcune indicazioni di base verrano ribadite, ma l'idea sara quella di porre l'attenzioni su situazioni altamente patologiche e sul modo di maneggiare strutture dati e funzioni. Nelle regole che seguono non e sempre possibile distinguere in modo netto tra loro le componenti relative alla correttezza, portabilita ed ecienza, per cui viena fornita una lista descrittiva unica, organizzata per tipo di istruzione ed operatore: istruzioni ANSI: in questo documento ci si riferisce solo ed esclusivamente al set di istruzioni previste dalla codica ANSI[Ame89]: tranne in casi specici, sono assolutamente da evitare concessioni ad estensioni del linguaggio standard; istruzioni di salto: e opportuno ribadire un concetto cardine della programmazione strutturata: evitare assolutamente l'uso dei goto; la medesima raccomandazione e valida, anche se in misura meno stringente, per break e continue. L'idea che sta alla base di queste proibizioni e che il programma perde di leggibilita, compromettendo anche eventuali sistemi di verica automatica della correttezza. L'istruzione break e ovviamente ammessa nel costrutto switch, mentre in altri casi, dovrebbe essere sostituita dall'uso di variabili booleane, tranne, eventualmente, nelle situazioni in cui il salto causato risulti facilmente individuabile (discorso analogo va fatto per la continue); inizializzazione di variabili: non inizializzare mai le variabili nel punto in cui sono dichiarate (ovviamente fanno eccezione le variabili statiche all'interno di funzioni) a meno di introdurre un opportuno commento tra le istruzioni, e l'eventuale inizializzazione va fatta subito prima del loro uso, in modo da avere sempre sotto controllo i valori con cui inizia una certa azione. Non e corretto fare assunzioni basate su valori di default assegnati dal compilatore in quanto non portabili e non stabili; uso di costanti: si ribadisce la necessita di non utilizzare valori numerici ma costanti simboliche, tranne nei casi in cui il signicato del valore e ovvio come in for(i = 0; i < MAX ITER; i++). Il valore della costante deve essere consistente con il suo uso per evitare cast impliciti. Nel caso di costanti logicamente omogenee l'uso di enum migliora la modicabilita del codice. In particolare, i puntatori vanno sempre confrontati con la costante NULL, sul cui valore non debbono essere fatte assunzioni e nel solo caso in cui il compilatore sia ANSI e il prototipo della funzione sia stato correttamente dichiarato, allora non e necessario farne il cast quando viene passata come argomento di una funzione; uso del preprocessore: il prepocessore risulta essere uno strumento tanto potente quanto fonte di errori e quindi meglio limitarne l'uso allo stretto indispensabile. Particolare attenzione va 12 che spiegano cosa fa una certa sezione di codice, non piuttosto come lo fa. Estremamente raccomandato e l'uso dei commenti per chiarire il ruolo di una costante o del campo di una struttura dati o di una variabile. Un esempio completo di le servira a chiarire molte delle asserzioni fatte: /* * * * * * * * * * * * * * * * */ type: main and unique functions group for the check_style program purpose: take a .c file in input and check the programming style author: P. Spiderman, [email protected] update: February 31, 1999: author June 31, 1999: Mr. Fantastic insert check of use of pointers libraries: -lm -lgulp headers: #define MAX_LEN 10000 /* max file length */ FILE *Fp; /* data file pointer */ /* * group: functions that parse the file * */ /* * * * * * * * */ int { type: procedure purpose: make a first level parsing of the file inputs: contents of data file outputs: error condition if .... first_parsing(char *data_array) char *last_position; /* scan the array searching the # characters */ while( read_from_array(data_array) != ERROR) { ... /* do some action */ } data = last_position; /* the last # position */ if( last_position == NULL) return(ERROR) ... } 11 { nel caso di presenza della funzione main() la descrizione del tipo avra la forma: \gruppo (principale o unico) di funzioni sviluppate per il programma application". il campo type viene anche applicato alla descrizione delle singole funzioni, e in questo caso specica se la funzione C e una function, una procedura semplice o una procedura complessa (vedi discussione a pag. 15); nel caso di header le il contenuto sar a: \header le (privato o pubblico) associato al le (nome del le)", dove la distinzione tra privato e pubblico sara chiarita nel paragrafo 2.1 purpose: questo campo si applica a tutti i tipi di commenti che stiamo trattando (eccetto il caso degli header le) e fornisce una descrizione dell'applicazione, del gruppo di funzioni o della funzione, a seconda dei casi. E ragionevole aumentare il livello di dettaglio e di informazione passando dalla descrizione della singola funzione a quella del le contenente la main() function; author(s): puo essere utile avere all'inizio di un le il riferimento all'autore con eventuale e-mail; version: il numero della corrente versione e la sua data di produzione. E auspicabile avere anche una breve descrizione delle sue nuove caratteristiche rispetto alle versioni precedenti; update: anche questo campo si applica solo al commento di inizio le, non e strettamente necessario, pero puo essere utile mantenere una traccia dei cambiamenti apportati e dell'indicazione di chi li ha eettuati e quando; libraries: utilizzabile anche questo solo nel commento iniziale e descrivente l'elenco delle librerie da linkare quando si utilizza il gruppo di funzioni del le; headers: analogo al precedente, si riferisce pero solo a moduli linkabili ma non eseguibili e riguarda l'inclusione di header le. Viene applicato anche ad header le di tipo pubblico ed il signicato in questo caso sara chiaro nel par. 2.1. group: rappresenta l'unico campo presente nel blocco di commento di un sottogruppo di funzioni all'interno di un le, in esso vengono descritte le funzionalita associate all' insieme di funzioni che seguono; inputs: applicabile esclusivamente al commento di descrizione delle singole funzioni per descrivere linguisticamente i parametri di input della funzione outputs: simile al campo inputs, solo che in questo caso viene descritto il signicato del valore di ritorno della function o le speciche di errore nel caso di procedure. commenti inlined si intendono tutti quei commenti introdotti all'interno del codice e che for- niscono un chiarimento su alcune azioni o dichiarazioni. In genere sono indentati allo stesso livello del codice ma in caso di commenti brevi possono essere posti sulla medesima linea dell'istruzione. Sono da evitare commenti ridondanti o quelli che specicano troppo in dettaglio cose che sono sucientemente evidenti dal codice, mentre sono fondamentali i commenti 10 file: group_of_mixed_functions.c /* initial comments about the program and/or the local functions */ /* comments about the first group of functions */ ... /* function definitions */ /* description for the function fun */ int fun() { ... } ... /* comments about the second group of functions */ ... /* function definitions */ L'introduzione di questi commenti di tipo generale risulta indispensabile per comprendere immediatamente quali sono gli obiettivi perseguiti da un gruppo di funzioni, come e organizzato il programma e cosa fa ogni singola funzione. E opportuno introdurre delle regole riguardo alla strutturazione ed al contenuto di queste linee di descrizione, introducendo dei campi descrittivi il cui contenuto variera in funzione dei tre casi di cui sopra. La forma del blocco di commento e quasi un puro fatto di gusto estetico, qui si preferisce una forma essenziale di blocco di commento: /* * * This is a comment block ! * */ Per cio che concerne i campi descrittivi si propongono i seguenti, in ordine di apparizione: type: quando applicato al commento di inizio le (con commento di inizio le si intendera, qualora non specicato altrimenti, il commento al le con le funzioni e non all'header) ne specica il tipo di funzioni contenute in esso: { se questo e un gruppo di funzioni concorrenti allo sviluppo di un programma specico, quindi dipendenti da altre funzioni ed header sviluppati per l'applicazione in questione e la main() function e situata in un le dierente, allora il tipo sara qualcosa come: \gruppo di funzioni speciche per il programma application (nome del le contenente il main())"; { se il modulo contiene sempre funzioni prive di un main() e sviluppate per un particolare proposito, ma autoconsistenti, ovvero tali da poterle eventualmente inserire in una libreria special-purpose, allora il tipo sara: \gruppo di funzioni sviluppate per il programma application archiviabili (archiviate) in una libreria (nome della libreria)"; 9 non utilizzare in alcun modo valori numerici all'interno del codice a meno che il loro signicato risulti ovvio dal contesto. Le costanti numeriche vanno codicate con espressioni simboliche mediante l'uso di #dene, const o enum, quest'ultimo fortemente consigliato per gruppi di costanti riferentesi ad una medesimo oggetto. In questo modo risultera possibile da una parte, modicare un valore in modo uniforme e con un'unica azione su un qualsivoglia insieme di le, e dall'altra fornire un chiaro signicato ad ogni valore numerico presente nel codice, evitando cos numeri magici di cui si perde presto memoria. In particolare le dimensioni degli array vanno gestite eplicitamente sempre tramite l'utilizzo di espressioni simboliche. alcuni tipi e costanti di uso comune vanno deniti in un opportuno le header. In particolare in C non esiste un tipo booleano, per cui va introdotto con la dichiarazione: typedef short boolean; Associato ad esso vi sono le costanti TRUE e FALSE che andranno denite come: #define TRUE #define FALSE 1 0 Pio risultare opportuno denire una costante simbolica ERROR da associare al valore di errore generico ritornato da una propria funzione. 1.1.4 Commenti I commenti costituiscono una componente essenziale di un programma e debbono descrivere in modo corretto e conciso le funzioni svolte e le modalita di realizzazione. E ragionevole distinguere due classi di commenti: quelli di intestazione e quelli che chiameremo inlined: commenti di intestazione : si intendono con questa espressione le linee di commento che debbono necessariamente essere introdotte all'inizio di: ogni le raggruppante un insieme di funzioni, ogni denizione di funzione, ogni sottogruppo logicamente omogeneo di funzioni, ogni header le. Il seguente esempio illustra la situazione: 8 Nel caso in cui il corpo del costrutto sia una singola istruzione, allora e lecito eliminare completamente le parentesi e conservare la sola indentazione, lasciando eventualmente una linea bianca dopo l'istruzione: for(i = 0; i < iterations; i++) h += i + 2; ... /* new instructions */ le struct e le union seguono la una regola analoga a quella dei costrutti, per cui andranno dichiarate nel seguente modo: struct i_am_a_struct { int first; int second; char third; }; ed analogamente nel caso di utilizzo con typedef; cercare di utilizzare sempre delle parentesi tonde per raggruppare operazioni omogenee e/o per esplicitamente imporre le regole di precedenza degli operatori: x = (var_correlated_to_b + b) + (c * d) + e; spazi bianchi : variabili ed operatori non vanno mai scritti senza inserire degli spazi tra essi, ad eccezione dei casi degli incrementi o decrementi pressi e/o postssi, degli operatori , e ; ed in relazione alle parentesi tonde ( ) : for(i = 0; i < number_of_iterations; i++) linee bianche di separazione sono necessarie tra la ne di una funzione e l'inizio della successiva; blocchi di istruzioni contigue ma logicamente separate vanno distinte tramite l'inserzione di una linea bianca 1.1.3 Strutture e tipi di dati In questo paragrafo verrano arontate le questioni derivanti dall'uso delle strutture dati, delle denizioni di nuovi tipi e delle costanti simboliche, ovvero di quelle componenti del linguaggio che ne aumentano la capacita espressiva e la compattezza. Anche in questo caso viene fornita una lista di regole in grado di coprire i casi piu interessanti e/o comuni: per evitare inutili e dannose dispersioni, tutta l'informazione relativa ad un particolare oggetto deve essere raggruppata in una struttura, creando cos un insieme omogeneo facilmente individuabile e modicabile. e consigliabile utilizzare typedef ogni qualvolta si denisce una struttura, in modo da creare un nuovo tipo di dato, con la propria semantica, e riferirsi nel seguito solo ad esso. 7 int fun(int float char first_arg, second_arg, third_arg) Nel caso di dichiarazioni di variabili, quelle tra loro non correlate andranno in ogni caso poste su linee dierenti; una lunga istruzione di assegnamento puo essere spezzata su piu linee, indentando a destra del segno di separazione tra lvalue ed rvalue: left_value = (first_function(argument) * second_function(argument)) / denominator; Analogamente, nel caso di costrutti con condizioni multiple descritte da espressioni lunghe, ogni singola condizione verra posta su di una riga dierente, indentando rispetto alla parentesi del costrutto condizionale: if( first_condition() * useful_variable && second_condition() * another_useful_variable) { ... Le medesime considerazioni si applicano ad elaborati for loops o a lunghe espressioni realizzate con l'operatore ternario ?: che risultano meglio visibili se poste su piu linee: var_to_assign = (var_1 == var_2) ? assign_1 : assign_2; parentesi : le strutture di controllo vanno scritte secondo lo schema: keyword { body } ovvero, nel caso dell'if: if( cond ) { if_body } else { else_body } ed in maniera analoga per while, for, do-while e lo switch: switch( expr ) { case value_1: body_1; case value_2: body_2; ... } 6 e` locale ma il tipo della struttura e` definito globalmente */ GlobalVar.FirstField = 2; /* anche questa espressione risulta essere priva di qualsiasi ambiguita` */ ... } Le costanti, denite tramite define, const o enum, vanno espresse nella forma: THIS IS A CONSTANT, ovvero totalmente in lettere maiuscole con degli underscore come separatori delle singole componenti. Le macro assumono questa stessa forma o in alternativa quella specicata per le funzioni, nel caso in cui esse siano realmente delle funzioni (non nel senso C), ovvero la loro azione non apporti modiche agli argomenti o ad altri parametri, ma si riduca ad una valutazione basata sullo stato degli argomenti stessi(su questo aspetto si tornera in seguito). Nel caso in cui una classe di funzioni acceda ad un medesimo oggetto, sico od astratto, allora e opportuno utilizzare una classe omogenea di nomi del tipo: AzioneOggetto(). Se ad esempio l' \oggetto" in questione e la velocita Speed, allora funzioni che eettuano azioni sulla velocita avranno la forma: SetSpeed(), ResetSpeed(), GetSpeed(). D'altra parte, se esiste una struttura dati specica attraverso cui le funzioni eettuano le loro azioni, allora questa si chiamera OggettoType; per cui nel caso del precedente esempio esistera una struttura dati con nome SpeedType che verra utilizzata dalle funzioni: int SetSpeed(SpeedType *speed). Si osservi che tale convenzione permette di evitare eventuali conitti con quella del tipo oggetto t generalmente usata nei moderni compilatori, dove, ad esempio, il tipo del dato che si riferisce ad una misura di tempo viene spesso indicato con time t. 1.1.2 Pretty-printing Con pretty-printing intendiamo l'insieme di regole governanti le modalita di stesura dei programmi e che quindi coinvolgono l'uso delle indentazioni, delle parentesi e degli spazi bianchi. Segue quindi un insieme di regole organizzato in funzione di questa suddivisione: indentazione : la regola principale e quella di indentare su di una medesima colonna tutte e sole le istruzioni che fanno parte dello stesso livello logico e cio si intende valido anche per i commenti; le variabili globali, le funzioni e le istruzioni di preprocessor vanno indentate in prima colonna; le liste di dichiarazione delle variabili o degli argomenti di funzione, quando eccessivamente lunghe, vanno organizzate su piu linee, nel seguente modo: char *a, b, c, d, foo, gnu; 5 del le di denizione, andranno scritti con la prima lettera in maiuscolo per quanto riguarda i nomi semplici, mentre, nel caso dei nomi composti, verra posta in maiuscolo ogni prima lettera di ciascun nome componente. D'altra parte le variabili e funzioni locali andranno scritte sempre interamente in minuscolo, frapponendo degli underscore nel caso di nomi composti. Per chiarire meglio le cose si consideri il seguente esempio, in cui si considerano due le, export.c ed import.c, contenenti gruppi di funzioni linkate assieme: export.c int import.c ThisIsGlobal; extern int ThisIsGlobal; extern int ExportedFun(void); void first_local_fun(void) { void second_local_fun(void); int this_is_local; { ... int local_var; } int ExportedFun(void) local_var = ThisIsGlobal; { ... ... ThisIsGlobal = ExportedFun(); } } E evidente che in questo modo risulta immediatamente chiaro se una variabile o funzione va ricercata all'interno del le corrente e se una o piu funzioni esterne possono eventualmente modicarne il valore. Discorso analogo va fatto anche per i tipi. Se viene utilizzata l'istruzione typedef per denire un tipo di dato globale e/o esportato, allora nel denire il nome del nuovo tipo e opportuno seguire le convenzioni appena descritte. Nel caso specico delle struct e delle unions si potrebbero utilizzare le medesime regole, applicate per o al solo nome della struttura e non strettamente ai singoli campi. Questa regola, che puo in alcuni casi rendere meno pesante la notazione, non risulta pero sempre completamente chiaricatrice, come illustra il seguente esempio: struct ExternalStruct { ... }; struct OtherExternalStruct { ... }; struct OtherExternalStruct GlobalVar; void local_fun() { struct ExternalStruct external; struct OtherExternalStruct other; ... external.first_field = 1; /* non capisco se la struttura, come tipo, e` definita localmente o globalmente, debbo cercare nel codice la definizione di external */ other.FirstField = 0; /* risulta subito chiaro che la variabile 4 NewFoo, x square ecc.; nel seguito ci si riferira, esplicitamente o implicitamente a tale suddivi- sione. Viene proposta una lista di regole (convezioni) da adottare nell'utilizzo dei nomi, arontando vari aspetti del problema e considerando diverse situazioni : la prima questione da arontare e quella concernente il signicato da associare agli oggetti individuati da un particolare nome. E di critica importanza utilizzare nomi che chiariscano in modo non ambiguo il ruolo svolto dalla variabile, dal tipo o dalla costante e l'azione effettuata dalla funzione o dalla macro. L'invito e a correre il rischio di essere prolissi pur di consentire una agevole lettura del codice, pensando soprattutto ad utilizzatori esterni. E rigorosamente obbligatorio utilizzare solo nomi e commenti in lingua inglese, per garantire un livello minimale di portabilita del codice. Un modo di contrarre la lunghezza dei nomi puo essere l'uso di acronimi o abbreviazioni dal signicato comunemente compreso, quali: Get, Put, Tmp, Var, Ptr, Dim, Vec, New, Mem, Add, Del, Str, ecc. ed i, j, k, l, .. per gli indici interi dei loop. Vengono riportati alcuni esempi d'uso: Uso Corretto Uso Scorretto check_port_status() ckpstat() AddItemToQueue() qadd() InstallNewObject() inst() tmp, temp t VecDim dim, d status s, stat robot_speed rs, speed, rspeed speed v Una delle obiezioni piu comuni, riguardo l'uso di nomi lunghi, concerne il dispendio di tempo che cio comporta in fase di scrittura del programma, ma, da una parte l'utilizzo di moderni strumenti di editing, come ad esempio emacs[Fre], che consente il completamento automatico dei nomi, e dall'altra i vantaggi ottenibili in fase di debugging, aggiornamento e distribuzione, riducono considerevolmente il peso di tale obiezione Una volta individuato un nome chiaro e opportuno evitare di renderlo ambiguo mediante l'introduzione di denizioni simili tra loro, del tipo: gnat, gnat1, gnat2, gant, Gnat, che ne rendono dicile la distinzione. Assolutamente proibito e l'uso dell'underscore come presso: gnat, stack, infatti questa risulta generalmente essere una caratteristica dei nomi deniti internamente dal compilatore. Un punto estremamente importante e quello del formato dei nomi in relazione al contesto ed all'oggetto a cui sono associati. In virtu della molteplicita di casi a cui e necessario fare riferimento, l'argomento verra sviluppato in punti separati. Per consentire una rapida individuazione del contesto di validita e denizione di una variabile o funzione, i nomi che fanno riferimento a variabili globali o funzioni esportate all'esterno 3 Capitolo 1 Il Linguaggio In questa sezione verranno analizzati gli aspetti relativi all'utilizzo delle varie componenti proprie del linguaggio C [KR88, Ame89] che concorrono, in modo uniforme, alla determinazione non solo della correttezza di un programma, il cui scopo primario e la funzionalita rispetto a delle speciche di progetto, ma anche ad una chiara comprensione del suo usso algoritmico, alla capacita di modicarne o migliorarne il comportamento, alla possibilta di utilizzo come costituente di una libreria di moduli funzionali. Nel successivo gruppo di sezioni verra proposto un utilizzo dei nomi, dello stile di formattazione del testo, dei commenti, delle strutture dati e delle funzioni, atto ad andare incontro a tutte queste esigenze. Questo primo insieme di regole di \buona programmazione" verra designato come livello semantico in quanto coinvolge una serie di aspetti legati alla interpretazione, alla lettura del codice, in un certo senso alla capacita di averne una chiara comprensione, se non globale, quantomeno locale. Complementare a questo aspetto di interpretazione e quello connesso con la correttezza sintattica del codice, con la sua portabilita e, non ultima, con la sua ecienza. Queste ultime sono tutte tematiche raggruppate nella classe degli aspetti sintattici in quanto appunto intimamente legati all'uso specico degli operatori, dei tipi di dato e dei costrutti. 1.1 Aspetti semantici 1.1.1 Uso dei nomi Un uso oppropriato dei nomi utilizzati all'interno di un programma e uno degli aspetti fondamentali per lo sviluppo, la comprensione e la manutenzione del software. Infatti l'utilizzo di nomi chiaricatori, dalla semantica non ambigua, permette di seguire con piu facilita il usso delle azioni svolte e di individuare con esattezza il ruolo e lo stato di ciascuna singola componente. L'adozione di nomi criptici d'altro canto rende estremamente dicoltosa la lettura \semantica" del programma, soprattutto, ma non esclusivamente, ad utilizzatori e/o sviluppatori terzi. Nell'appendice A viene brevemente presentato, a mo d'esempio, l'insieme di regole dello stile ungherese, ovvero uno tra i piu diusi e formalizzabili stili proposti. I nomi possono essere convenientemente suddivisi in due classi: nomi semplici e nomi composti; nella prima rientrano quelli del tipo: Var, gnat, foo, x, mentre nella seconda: gnat var, 2 Introduzione Le regole che verranno proposte sono basate, oltre che su un'esperienza personale, sull'analisi di alcuni dei molteplici documenti [Ame89, MAI93, ESA91, SH91, GFM+ 94, CEK+ 95, DLK95, Hor90, KR88](in particolare [MAI93]) prodotti nel tentativo di denire un insieme di regole di stile e di organizzazione del codice capaci di migliorarne signicativamente la qualita. Con la parola qualita si riassume tutta una serie di caratteristiche di cui si occupa in modo specico l'ingegneria del software [GFM+ 94, GJM91]: adabilita, correttezza, robustezza, sicurezza, prestazioni, usabilita, vericabilita, manutenibilita, riusabilita, comprensibilita, interoperabilita, produttivita, ecc. Lo stile della programmazione concorre insieme ad altri numerosi fattori ad inuenzare la qualita, e, denire delle regole opportune per la stesura del codice, e solo il primo passo nel lungo ed complesso processo di ottimizzazione del software. Dato lo stato di notevole fermento intellettuale in cui versa lo studio dell'ingegneria del software, e comprensibile il fatto che non esistano numerosi standard o tecniche consolidate ed universalmente accettate, e cio si riette in special modo anche per cio che concerne l'argomento di questo lavoro: sono state prodotte varie proposte, ciascuna con i propri pregi e difetti, ma non esiste uno standard de-facto a cui attenersi. L'insieme di regole qui proposto e stato individuato avendo in mente programmatori non necessariamente professionisti, quali se ne trovano spesso in ambito di ricerca scientica, che si trovino nella necessita di sviluppare non trascurabili quantita di codice suscettibile di essere integrato in librerie modicabili nel corso del tempo e che vadano a costituire un patrimonio comune a ricercatori di molteplici ambiti. Il lavoro e stato svolto focalizzando l'attenzione su due aspetti dominanti: il linguaggio in se [KR88, Ame89], le cui caratteristiche determinano: la leggibilita (componente semantica), dipendente dall'uso dei nomi, delle strutture dati, dell'indentazione, dei commenti e della strutturazione; la correttezza, portabilita ed ecienza (componente sintattica), le quali risultano inscindibilmente collegate all'uso degli operatori e dei costrutti; l'ambiente ovvero la localizzazione ed il contenuto dei le sorgente e degli header, la produzione e l'utilizzo di librerie, gli strumenti di compilazione, la documentazione allegata. 1 Indice Introduzione 1 1 Il Linguaggio 2 1.1 Aspetti semantici 1.1.1 Uso dei nomi 1.1.2 Pretty-printing 1.1.3 Strutture e tipi di dati 1.1.4 Commenti 1.2 Aspetti sintattici : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 2 L'ambiente 2.1 I le sorgente 2.2 La struttura delle directory e la compilazione 2.3 La documentazione 2 2 5 7 8 12 17 : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 17 22 25 Ringraziamenti 25 Bibliograa 26 A Lo stile Ungherese 28 i Regole di stile e di organizzazione per lo sviluppo di programmi in C Gianni Di Caro per conto del Dipartimento di Matematica, Universita di Trento via Sommarive 14, 38050 - Trento [email protected] Abstract Lo scopo di questo documento e quello di fornire una serie di suggerimenti riguardanti lo stile, l'organizzazione, la documentazione e gli accorgimenti da utilizzare nella stesura di codici di programmazione scritti in linguaggio C. Tali speciche risultano particolarmente importanti per progetti che richiedano l'integrazione ed il riutilizzo di moduli software sviluppati in ambito di ricerca scientica da istituzioni o persone in tempi e luoghi diversi. Le regole proposte rappresentano un insieme coerente ed elaborato delle regole piu diuse nell'ambito della programmazione in C.