LINGUAGGI
PROGRAMMARE IN
C
Patti chiari, amicizia lunga
di Mr. Lambda
Avviarsi sulla luminosa strada della programmazione
con il linguaggio C è possibile a patto che sappiamo adattare le
nostre conoscenze di programmazione al nuovo ambiente oppure ci accostiamo
fin dall'inizio con idee chiare e precise alle possibilità offerte
da questo sorprendente linguaggio. Ed è proprio questo lo scopo
di questo nostro incontro. Quindi inforcate i vostri occhiali da sole e
seguiteci.
Negli articoli che via via vi introdurranno al linguaggio,
il C non sarà lasciato a se stesso, ma volta per volta verrà
messo in relazione ad altri linguaggi come il BASIC, il Pascal, l'Assembly,
ecc. E questo perché riteniamo che alcuni aspetti del C saranno
facilmente appresi da coloro che conoscano sufficientemente un altro linguaggio
di programmazione. Prendiamo, per esempio, la funzione C chiamata printf()
che stampa messaggi sullo schermo. Il suo nome significa ' print formattato
'. Se la parola ' funzione ' vi e' nuova, ma non lo dovrebbe essere, potete
sostituirla con le parole ' subroutine ' oppure ' procedura '. Una funzione
e' una parte di un programma che esegue un determinato lavoro, come stampare
messaggi sullo schermo, oppure leggere file dal disco.
Ma prima di continuare concediamoci alcuni brevi riferimenti
storici. La maggior parte delle Introduzioni al linguaggio C comprendono
riferimenti storici per parecchie buone ragioni. Innanzitutto calarsi nella
giusta atmosfera e quindi comprenderne i rapporti con l'evoluzione informatica.
Brian Kernighan e Dennis Ritchie hanno scritto il libro "The C Programming
Language", che voi potete leggere nella traduzione offerta dalla Jackson,
nel 1977. Ma il linguaggio C era stato già sviluppato agli inizi
degli anni settanta sotto il sistema operativo Unix. Infatti, come già
dovreste sapere, a tutt'oggi il sistema operativo Unix è scritto
proprio in C e il C a sua volta adotta alcune convenzioni del sistema Unix,
come il modo standard dei programmi in C di accettare input e inviare output.
Ma c'è dell'altro. E ci sono altre ragioni per questo. C e Amiga
sono strettamente imparentati. AmigaDOS, il sistema operativo di Amiga,
è basato sul sistema operativo TRIPOS. Martin Richards della Cambridge
University, uno dei ricercatori che ha contribuito maggiormente a TRIPOS,
ha pure scritto un linguaggio chiamato BCPL, che a sua volta ha ampiamente
influenzato la formulazione del linguaggio C e insieme al linguaggio C
è stato utilizzato per lo sviluppo del sistema operativo di Amiga.
Ma non basta. Commodore-Amiga ha utilizzato il C per scrivere Intuition
e Workbench, il software a window e menu di Amiga.
Ormai si può dire, senza timore di smentite, che
il C è divenuto il linguaggio preferito tra i programmatori per
tutte le sue straordinarie qualità e i pochi difetti. Il C è
rapido, soddisfacente per la maggior parte delle applicazioni. Il C è
veramente versatile e flessibile. Permette al programmatore di fare quasi
ogni cosa. Mentre il Pascal può rifiutarsi di permettere l'assegnamento
di una variabile in virgola mobile a una variabile carattere, e il BASIC
si rifiuta di assegnare una variabile stringa a una variabile intera, il
C permette con facilita tali assegnamenti. Per quanto siano possibili,
queste mescolanze di tipi di dati non sono consigliabili, ma in ogni caso
il C assume che voi sappiate che cosa state facendo e compie esattamente
ciò che gli avete chiesto.
Questa caratteristica del linguaggio viene chiamata '
typing '. IL Pascal e' un linguaggio fortemente tipizzato, dal momento
che l'operatore ' : = ' non ci consente di assegnare un numero in virgola
mobile a una variabile carattere. Il BASIC e' meno fortemente tipizzato
, dal momento che ' = ' assegnera' numeri in virgola mobile ad interi e
viceversa. Tuttavia il C è debolmente tipizzato, anche se offre
una varietà e complessità di tipi di dati di tutto rispetto.
Il compilatore può produrre un avvertimento o warning nel caso che
i tipi di dati non corrispondano, ma continuerà a fare il suo dovere
fino in fondo e il programma girerà ugualmente.
Ma se qualcuno a questo punto sta già svenendo
o giù di lì per la terminologia utilizzata, vi proponiamo
i seguenti commenti. I numeri in virgola mobile sono numeri con una componente
frazionale. l computer devono rappresentare 7,302 come un numero a virgola
mobile, mentre 3751 può essere memorizzato come un numero intero.
E importante osservare che ciascun tipo una quantità diversa di
spazio di memoria per essere memorizzato. I programmatori più smaliziati
considerano il comportamento del C rispetto ai tipi di dati come una qualità
del linguaggio e vi si riferiscono in termini di flessibilità. Coloro
invece che si avvicinano al C dopo essersi abituati a programmare in Pascal
o BASIC considerano questa libertà del linguaggio come una disgrazia.
Il C, infatti, ha pochi avvertimenti del tipo abituale in Pascal oppure
BASIC. Anche il C è un linguaggio compilato esattamente come il
Pascal o il Modula-2 e diversamente dal BASIC, che è un linguaggio
interpretato. E se voi conoscete il Pascal oppure il Modula- 2 apprenderete
molto più facilmente il C. Quando si parla di linguaggio ' compilato
' significa che un programma viene convertito in un'altra forma prima di
essere eseguito. Per i linguaggi compilati, un programmatore crea un testo
sorgente o codice sorgente con un editor, e quindi compila il codice sorgente
con il compilatore, producendo un codice oggetto o modulo oggetto. I moduli
oggetto devono essere poi linkati con un programma linker per produrre
un programma eseguibile. Il compilatore traduce il codice sorgente nel
linguaggio macchina del computer, e produce una lista di variabili e funzioni
utilizzate all'interno di questo programma. Per esempio, un programma puo'
utilizzare la funzione printf(). Questa funzione può far parte del
linguaggio, ma deve essere definita da qualche parte al di fuori dal codice
sorgente, a meno che non la ridefiniate voi per l'occasione. La funzione
printf() viene detta appunto esterna o non risolta, dal momento che il
compilatore non può generare codice macchina per essa. Un modulo
oggetto è composto da questa mescolanza di linguaggio macchina e
liste di riferimenti non risolti (unresolved references).
Dopo che il programma è stato compilato, tutti
questi riferimenti alle funzioni e alle variabili devono essere risolti
con il linker.
Se voi comprerete un compilatore C, insieme con esso avrete
a disposizione diverse collezioni di funzioni pre-scritte e pre-compilate,
chiamate librarie. Le funzioni standard come printf() sono incluse in una
libreria, mentre sin(), cos() e altre funzioni matematiche sono incluse
in un'altra.
Il modulo oggetto prodotto precedentemente viene linkato
con una o più librerie. Il linker utilizza la lista delle funzioni
e variabili non risolte del modulo oggetto per individuare ciascuna libreria
di cui servirsi. Se una funzione viene trovata in una libreria, vengono
cercati il suo codice macchina e la sua lista di riferimenti non risolti,
fino a trovare e risolvere tutti i riferimenti. Quindi solamente il linker
può produrre un programma eseguibile. Questo file viene composto
solamente dalle funzioni che necessarie al programma. Le altre funzioni
di libreria vengono lasciate fuori.
Se il linker non può trovare un riferimento, non
può produrre un programma corretto, dal momento che manca qualcosa
che costituisce il programma. Il programma linker si fermerà dopo
aver stampato una lista di funzioni e/o variabili esterne (unresolved externals)
che non riesce a trovare nelle librerie a disposizione. In questo caso,
il programmatore dovrebbe esaminare l'output del linker ed effettuare le
correzioni del caso. Spesso il testo sorgente contiene errori di ortografia.
Se printf() è stato scritto prnitf(), il linker non riuscirà
mai a trovare prnitf(), anche se è in grado di individuare printf().
Se, per esempio, sin() e cos() compaiono nella lista di output del linker,
il programmatore può anche aver dimenticato di includere la libreria
di funzioni matematiche. La vostra abilità di interpretare correttamente
l'output del linker si svilupperà con il tempo.
Questo processo di compilazione e linkaggio vi costringe
a scrivere molto nel CLI. Voi potete però servirvi sia di speciali
comandi presenti nei compilatori stessi - LC nel compilatore Lattice C
per esempio -, sia di particolari file batch o execute chiamati MAKE. Nell'articolo
dedicato al C del numero precedente della rivista vi abbiamo appunto presentato
un'utility MAKE che vi permette di utilizzare efficacemente il compilatore
Lattice C 3.10. Se voi avete creato un programma C chiamato Prova.c e volete
compilarlo, è sufficiente che scriviate sulla linea comandi del
CLI.
EXECUTE MAKE PROVA
E il gioco è fatto. Come vi abbiamo già spiegato
il file batch o execute contiene tutti i comandi CLI necessari per la compilazione
e il linkaggio di un programma C, evitandovi un mucchio di digitazione
ad alto rischio!
Primi passi
Esaminiamo con attenzione un breve e semplice programma
in C, BASIC, e Pascal. Il programma moltiplicherà due numeri e stamperà
il risultato. Eccolo in C:
#include "stdio.h"
/* un programma per moltiplicare due numeri */
main()
{
int a,b,c;
a = 3;
b = 4;
c = a* b;
printf("Il risultato è %d\n",c);
}
Eccolo invece in BASIC:
5 REM un programma per moltiplicare
6 REM due numeri
10 a = 3
20 b = 4
30 c = a * b
40 print "Il risultato è ";c
E in Pascal:
program add(input,output);
integer a,b,c;
(* un programma per moltiplicare due numeri *)
begin
a:= 3;
b:= 4;
c:= a*b;
writeln('l1 risultato:',c);
end.
Per prima cossa potete notare che un programma in C ha
molta puntaggiatura. Il C utilizza la puntaggiatura nello stesso modo in
cui Pascal utilizza ' begin ' e ' end '. Questo sembra rendere i programmi
in C piu' difficili da leggere dei programmi in Pascal, per esempio. Mentre
il Pascal utilizza ' begin ' e ' end ' per delimitare l'inizio e la fine
delle procedure e delle funzioni, il C utilizza le parentesi graffe ' {
' e ' } '. Inoltre, il Pascal e il C separano i commenti al programma dal
codice ancora con l'utilizzo della punteggiatura. Il C utilizza la coppia
di ' /* ' e ' */ ', mentre il Pascal utilizza ' (* ' e ' *) '. Il BASIC
utilizza invece lo statement REM all'inizio della linea per segnalare all'interprete
un commento. Coloro che utilizzano AmigaBASIC sanno pero' che i commenti
possono venire segnalati al compilatore anche attraverso l'utilizzo dell'apostrofo
( ' ) e che non sono necessari i numeri di linea. Quasi tutto cio' che
viene detto a proposito del Pascal può essere applicato al Modula-2.
Sia il Pascal che il C sono linguaggi compilati. Osservate
come il Pascal e il C dichiarino esplicitamente negli esempi le variabili
a, b e c. Il Pascal scrive ' integer' dove il C scrive ' int ' per dichiarare
una variabile integera.
Questo e' comune ai linguaggi compilati. Un compilatore
infatti ha bisogno di conoscere il tipo (type) della variabile prima di
utilizzarla nel programma. Questa conoscenza verrà utilizzata per
eseguire correttamente le operazioni, come addizioni o moltiplicazioni,
quando questa variabile viene utilizzata successivamente, poiché
numeri interi e numeri a virgola mobile vengono utilizzati differentemente
e occupano un diverso spazio di memoria. L'esempio in BASIC non necessita
di dichirazioni di tipo esplicite per ciascuna variabile. Il BASIC è
un linguaggio interpretato. (Naturalmente esistono anche ottimi compilatori
per il BASIC come quello offerto dalla Microsoft per AmigaBASIC.)
Quando voi digitate RUN, il computer analizza il testo
di ciascuna linea per eseguire successivamente i diversi passi del programma.
Se il BASIC incontra una variabile che non riconosce, asssume che quella
variabile sia un numero in virgola mobile. Quindi nel nostro esempio a,b
e c sono variabili in virgola mobile.
Il C ha pure anche altri tipi di variabili oltre a ' int
': ' float ' per virgola mobile, e ' char ' per variabili carattere e altre
che vedremo a suo tempo. Ciascun tipo possiede un intervallo valido di
valori. Per esempio. ' int ' e' limitato a più o meno due bilioni
in Amiga, mentre ' char ' e' limitato ai valori tra +127 e -128. In realtà
per questi intervalli bisogna sempre tenere anche conto delle implementazioni
reali dei vari compilatori.
I tipi ' integer ', cioe' interi come ' char ' e ' int
', possono venire modificati dalla parola ' unsigned ' - senza segno -,
creando in tal modo i tipi ' unsigned char ' e ' unsigned int '. Questi
tipi hanno rispettivamente intervalli tra 0 e 255 e tra 0 e oltre i 4,2
bilioni. Se voi conoscete bit e byte, potrete dedurne che questi valori
possono essere rappresentati in 8 e 32 bit. Ciascun tipo ha diversi operatori
validi, come addizione e sottrazione, moltiplicazione e divisione. Diversamente
dal BASIC e dal Pascal, il C e' molto flessibile relativamente alla possibilità
di operare con essi. Per esempio, potete addizionare variabili ' char '
e variabili ' int '. Il valore di un ' char ' e' il valore ASCII del carattere
memorizzato all'interno di esso. In BASIC potreste ottenere questo valore
utilizzando la funzione ASCO, in Pascal potreste invece utilizzare ORDO.
Avrete senz'altro osservato che le linee del programma
in C e in Pascal terminano con un punto e virgola. Questa è un'altra
caratteristica dei linguaggi compilati. Il ' ; ' comunica al compilatore
che il programmatore ritiene che la linea sia completa. Infatti, le linee
dei programmi in C possono essere distribuite su diverse linee fisiche
senza alcuna conseguenza dannosa.
printf
(
"Il risultato e' %d"
,
c
)
;
Il compilatore analizza la linea nello stesso modo. E questo
non è possibile con la maggior parte degli interpreti BASIC.
Incominciamo a prendere in considerazione il codice C.
#include "stdio.h"
È un comando per una parte speciale del compilatore
chiamata preprocessore. Questa linea compilatore: "a questo punto compila
anche il file programma chiamato stdio.h". Questo file contiene le dichiarazioni
delle variabili e delle funzioni utilizzate da printf() e altre funzioni
standard di I/O. Il preprocessore ha anche altre caratteristiche che considereremo
in seguito.
Osserviamo ora attentamente la linea che contiene la funzione
printf(). printf() ha due argomenti, una stringa di caratteri contenente
il testo "Il risultato è %d\n" è il nome di una variabile
intera, c. Per un programmatore in C questi sono gli argomenti passati
alla funzione, gli argomenti della funzione oppure ancora i valori passati
alla funzione. Come avrete ormai capito gli argomenti vengono collocati
tra le parentesi e separati dalle virgole.
Il primo argomento passato alla funzione printf() descrive
il formato del testo che noi vogliamo visualizzare sullo schermo. Esso
contiene due parti che potrebbero esserci poco familiari, entrambe presenti
verso la fine del testo tra virgolette. (Ah, se voi conosceste il FORTRAN,
potreste ora contare sulla conoscenza del comando FORMAT!) Il %d significa
"qui stampa un valore intero". Il \n significa "qui stampa una carattere
di accapo", che dovrebbe muovere il cursore sulla linea successiva, esattamente
come succede quando voi premente il tasto Return. Se noi desideriamo visualizzare
due valori utilizzando printf(), possiamo scrivere:
printf( "Noi abbiamo %d mele e %d pere.\n",mele, pere);
Per ottenere questo risultato:
Noi abbiamo 7 mele e 5 pere.
Se naturalmente le variabili ' mele ' e ' pere ' contengono
rispettivamente i valori 7 e 5.
Noi potremmo chiedere a printf() di stampare un valore
in un campo di determinata ampiezza. Se si utilizza %4d come formato per
gli interi, il numero sara' visualizzato con gli spazi necessari per riempire
quattro posizioni carattere. La funzione printf() ha altri comandi di formattazione
- per una visione complessiva e dettagliata consultare sempre il manuale
del proprio compilatore -, come %x, per esempio, che stampa valori in esadecimale
oppure %C, utilizzato con le stringhe formattate per visualizzare un valore
come carattere ASCII, similmente al comando BASIC CHR$().
Le funzioni vengono utilizzate estensivamente in C; e
alle funzioni pre-scritte che il linguaggio vi mette a disposizione ben
presto si affiancheranno le funzioni che voi stessi scriverete per i vostri
programmi. Se voi avete utilizzato il Pascal avrete acquistato una certa
familiarità con le procedure e le funzioni. Ebbene le funzioni C
ricordano proprio queste ultime dal momento che ritornano tutte un valore.
(In realtà esiste la parola riservata VOID che preclude alle funzioni
la possibilità di ritornare valori, ma di ciò in seguito,
quando ne saprete molto di più.) Le funzioni C, comunque, non possono
venire nidificate come succede nel Pascal con le procedure e le funzioni.
Se voi non avete mai utilizzato il Pascal, pensate alle
funzioni BASIC SIN() e COS(). Queste funzioni ritornano un valore in virgola
mobile basato sul loro argomento, e cioè il numero tra parentesi.
Tutte le funzioni C ritornano un valore di un determinato tipo. Eccovi
un frammento di programma che dichiara una funzione che ritorna un intero,
il prodotto di due argomenti interi:
/* una funzione per moltiplicare due numeri */
int mult(a,b)
int a,b;
{
int c; /* una variabile locale */
return(c);
}
La prima linea dichiara una nuova funzione chiamata mult(),
che ha due argomenti, a e b, entrambi interi. Una dichiarazione di funzione
può essere suddivisa in diverse parti. Prima, il tipo della funzione.
Qui, noi abbiamo utilizzato ' int '. Se nessun tipo viene dichiarato, il
C assume che la nuova funzione sia di tipo ' int '. Seconda, il nome della
funzione, ' mult '. Tutte le dichiarazioni delle funzioni hanno un'insieme
di parentesi, sia che la funzione abbia argomenti oppure no. Questa funzione
ha due argomenti. Il tipo di ciascun argomento presente tra le parentesi
deve venir dichiarato prima della parentesi graffa aperta. La parentesi
graffa aperta indica al compilatore l'inizio del codice della funzione.
La linea dopo la parentesi graffa aperta dichiara una
variabile locale chiamata c. Se voi conoscete il Pascal, le variabili locali
in C lavorano nello stesso modo che in Pascal, tenendo a mente pero' la
restrizione che le funzioni non possono essere nidificate in C. Se voi,
invece, non conoscete il Pascal, una variabile locale è una variabile
a cui non si può accedere da un'altra procedura o funzione in un
programma, anche se un'altra funzione dichiara una variabile locale con
lo stesso nome. Il valore di una variabile locale è conosciuto solamente
durante l'esecuzione di una variabile. Al termine dell'esecuzione della
funzione, la variabile locale scompare, e la memoria che essa ha utilizzato
vien riutilizzata.
Il ' c = a * b ' dovrebbe essere chiaro, qualsiasi linguaggio
conosciate. La linea ' return(c); ' rappresenta la fine della funzione
mult(). Il valore ' c ' viene ritornato alla funzione che ha chiesto di
utilizzare la funzione multo. Vediamo ora un programma completo in C che
utilizza la funzione mult() descritta sopra.
#include "stdio.h"
/* una funzione per moltiplicare due numeri */
int mult(a,b);
int a,b;
{
int c; /* una variabile locale */
c=a*b;
return(c);
}
/* Tutte le variabili dichiarate all'esterno delle
dichiarazioni della funzione sono variabili
globali, che possono essere utilizzate da
tutte le funzioni
*/
int t; /* una variabile globale */
main()
{
int r,s; /* variabili locali */
r = 3;
s = 4;
t = mult(r,s);
printf("Il risultato e' %d\n" ,t);
}
In questo programma, la funzione main() chiama mult() con
i valori memorizzati in ' r ' e ' s '. Ipotizzando che ' r ' contenga 3
e ' s ' contenga 4, la funzione mult() ritornera' il valore 12. All'interno
della funzione main() il valore 12 verrà memorizzato nella variabile
globale di main() chiamata ' t '.
Un programmatore spiegherebbe cio' che è accaduto
dicendo che la funzione mult() è stata ' chiamata ' dalla funzione
main() e il valore 12 e' stato ' ritornato ' a main(). Poiché il
C e' basato su funzioni e chiamate di funzioni, i programmatori spesso
utilizzano queste espressioni. Le espressioni 'chiamata' e 'valore di ritorno'
alludono alla struttura fondamentale del C. Entrambe le espressioni vengono
utilizzate nello stesso contesto per programmi in linguaggio macchina.
E ora a main().
La dichiarazione della funzione main() non ha argomenti,
e nessun tipo specifico vene dichiarato, in questo modo main() ritornera'
un ' int '. main() e' una funzione speciale. Quando il programma C viene
avviato dal sistema operativo, main() è la prima funzione che viene
eseguita. A parte questo, main() non è in alcun altro modo differente
dalle altre funzioni C.
Ma ritorniamo alle veriabili. L'opposto di locale è
globale. Le variabili globali sono tutte quelle variabili che vengono dichiarate
all'esterno di tutte le funzioni del programma. D'altra parte le funzioni
globali vengono dichiarate nello stesso modo delle variabili locali. La
sintassi è la stessa: prima il tipo - int, per esempio -, quindi
i nomi delle variabili, separati da virgole, se ce ne sono più di
una, e per finire la terminazione ' ; '. Le variabili globali possono venire
lette e modificate da qualsiasi funzione del programma. La maggior parte
dei programmi presentano una mescolanza di variabili locali e variabili
locali. Le variabili vengono utilizzate per calcoli intermedi, dal momento
che scompaiono quando la funzione termina. Le variabili globali vengono
utilizzate per i dati piu' permanenti, e per quei dati che diverse funzioni
possono avar necessità di utilizzare. Se una funzione dichiara una
variabile locale con lo stesso nome di una variabile globale, la variabile
locale ha la precedenza.
Nel nostro prossimo incontro approfondiremo l'utilizzo
del preprocessore, e come dichiarare altri tipi di variabili. Ci introdurremo
nell'argomento fondamentale dell'I/O considerandone le funzioni a disposizione.
E forse ci diletteremo con le funzioni speciali di Amiga per le creazione
di window.
|