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.
Scarica

Lo stile Ungherese