Sei sulla pagina 1di 6

Guida Linux: il linguaggio C

Il primo programma C

22.1 Un programma minimale scritto in C

Il piu' piccolo programma che e' possibile scrivere in C e' il seguente:

main(){}

ma lo stesso programma puo' essere scritto come segue:

main

(

)

{

}

in sostanza non e' importante la posizione dei simboli ma e' importante che ogni parentesi aperta venga poi chiusa. Questo programma non produce alcun risultato in quanto non contiene istruzioni: e' un programma vuoto. Inoltre produce un errore di tipo warning (avvertimento) da parte del compilatore. Il messaggio potrebbe essere qualcosa del tipo: 'warning: function should return a value' ma per ora non ce ne occuperemo. I messaggi di tipo warning sono degli avvertimenti in quanto generalmente non bloccano la creazione dell'eseguibile ma sono solo un avvertimento per il programmatore in quanto potenzialmente potrebbero produrre un programma eseguibile errato e malfunzionante. Ad ogni modo normalmente i messaggi di tipo warning possono essere ignorati. Il costrutto 'main()' definisce una funzione. Qualsiasi programma scritto in C puo' contenere una o piu' funzioni: main() e' la funzione principale ed e' obbligatoria. A questo punto e' arrivato il momento di seguire la tradizione:

#include <stdio.h> main()

{

printf ("HELLO WORLD");

}

Al contrario del primo programma, quest'ultimo produce un risultato: visualizza a video la stringa "HELLO WORLD". Si tratta del primo programma funzionante scritto in C: una volta compilato lo si puo' eseguire. In realta' sarebbe piu' corretto scrivere 'int main(void)' al posto di 'main()' ma questi dettagli saranno discussi in seguito. Per scrivere un programma e compilarlo occorre aprire un qualsiasi programma di scrittura di testi (text editor) come ad esempio Kedit (corrisponde al blocco note o notepad di Windows) e scrivere il codice visto sopra. Salvarlo come 'prova.c'. Lanciare il compilatore gcc con il comando:

gcc prova.c

se tutto e' stato eseguito alla lettera si otterra' il file eseguibile a.out. Digitando 'a.out' verra' scritta a video la stringa "HELLO WORLD". Per default il compilatore Linux produce il file eseguibile a.out. Per produrre un eseguibile con un nome diverso occorre usare il comando:

gcc -o prova prova.c

a questo punto verra' creato il file eseguibile 'prova'. Attenzione, una piccola informazione banale per i

piu' esperti ma non troppo ovvia per chi non ha mai scritto un programma: non e' possibile usare editor di testi che non sono puro ASCII come ad esempio Word su Windows oppure OpenOffice su Linux. Infatti questi programmi non sono degli editor di testi ASCII ma dei programmi di tipo word processing ed inseriscono nel testo dei caratteri di controllo invisibili che servono a formattare il testo

(caratteri di controllo per: grassetto, font, allineamento paragrafi, indentazioni, formattazione titoli etc). Questi caratteri non sono visibili in quanto non stampabili ma esistono: il compilatore li leggerebbe e andrebbe su tutte le furie! ;o). Viceversa gli editor di testo ASCII scrivono solo quello che viene digitato. Ma torniamo al programma che visualizza 'HELLO WORLD". La prima riga del programma e' una direttiva per il compilatore, cioe' un comando impartito al compilatore che ha come

scopo l'inclusione (da qui include) all'interno del file che contiene il nostro programma, del file 'stdio.h'.

I file che terminano con '.h' sono degli header file cioe' dei file di intestazione che contengono delle

informazioni necessarie al programma. In particolare 'stdio.h' e' l'header file delle funzioni di i/o della libreria standard (da qui il nome stdio, cioe' STanDard I/O). I/O significa Input/Output, e rappresenta

Guida Linux: il linguaggio C

tutte le operazioni che accettano dei dati in input (ad esempio dalla tastiera) e tutte le operazioni che producono un output (ad esempio a video). Le librerie standard sono quelle librerie che contengono funzioni che 'funzionano' (ooops che gioco di parole!) su qualsiasi sistema, cioe' non sono legate ad una particolare implementazione. In altre parole una funzione come 'printf' funzionera' su sistemi Linux, Windows, Unix e via dicendo. Vedremo in seguito cosa contengono effettivamente gli header file, per ora e' sufficiente sapere che forniscono delle informazioni importanti. La seconda riga del programma contiene la scritta 'main()'. La parola main e' obbligatoria, cosi' come sono obbligatorie le parentesi tonde. Una parola seguita da due parentesi tonde identifica una funzione, la parola main in particolare identifica la funzione principale, cioe' il programma vero e proprio. Le parentesi graffe aperta e graffa chiusa delimitano un blocco di istruzioni. Su linux la parentesi graffa aperta viene prodotta premendo i tasti 'Alt Gr' e '7' contemporaneamente, mentre la parentesi graffa chiusa viene prodotta premendo i tasti 'Alt Gr' e '0' contemporaneamente. Su Windows invece occorre premere il

tasto 'Alt' e digitare la cifra '123' per produrre la parentesi graffa aperta ed il tasto 'Alt' e la cifra '125' per produrre quella chiusa. Infine, la terza riga contiene il codice del programma vero e proprio: printf ("HELLO WORLD");. Analizzando questa riga si intuisce che e' una specie di istruzione che stampa a video la stringa 'HELLO WORLD'. Osservando meglio pero', si notano le parentesi tonde aperta e chiusa

e diventa chiaro che 'printf' non e' una istruzione in linguaggio C ma e' una funzione di libreria, in particolare e' una funzione della libreria standard 'stdio'. Eliminando la prima riga del programma

(#include ) il compilatore potrebbe produrre un errore in quanto potrebbe non essere in grado di riconoscere la funzione printf. Perche' potrebbe? Perche' alcuni compilatori includono di default (default

= impostazioni predefinite, comportamento predefinito) gli header della libreria standard mentre altri

no. Normalmente un header file fornisce informazioni sulle funzioni usate all'interno del programma: se tale file non viene specificato si ottiene un messaggio di errore del compilatore che non riconosce la

funzione usata. Con la funzione printf cio' potrebbe anche non avvenire in quanto come dicevo alcuni compilatori includono automaticamente l'header file stdio.h. Percio' i file header vanno sempre specificati all'interno del programma. In generale la direttiva '#include ' ordina al compilatore di

copiare il contenuto del file 'qualcosa.h' all'interno del programma. Per default se si usano i simboli '<'

e '>' il compilatore prelevera' i file '.h' da una directory include ben precisa (generalmente

/usr/include). Ad ogni modo e' possibile usare gli apici doppi al posto dei simboli '<' e '>': in questo caso i file header vengono cercati prima di tutto nella directory corrente. Cio' puo' essere utile se si vuole ad esempio creare un proprio file header. Per specificare una directory diversa si possono usare i doppi apici specificando il path ad esempio: #inlcude "/home/mau/c/include/miofile.h". Il carattere '#' (pound negli USA, hash fuori degli USA e cancelletto in Italia) identifica una direttiva, cioe' un comando indirizzato al precompilatore. Un'osservazione finale: la riga che contiene la funzione printf e' terminata dal carattere ';' (punto e virgola). Questo carattere delimita la fine di una istruzione ed e' obbligatorio.

Piu' istruzioni vengono delimitate all'interno di un blocco mediante le parentesi graffe mentre una sola istruzione e' delimitata dal punto e virgola. Omettendo questo carattere nel programma precedente verra' prodotto un errore dal compilatore. Il programma appena visto non contiene istruzioni in linguaggio C vere e proprie ma unicamente una chiamata ad una funzione di libreria standard (printf).

Le parole 'chiave' che costituiscono il linguaggio C non sono moltissime, appena 32 secondo la prima

versione del C (standard ANSI C89):

if, else, struct, break, union, void, while, signed, unsigned, long, enum, for, register, extern, switch,

typedef, volatile, int, float, const, do, static, double, auto, char, return, goto, sizeof, short, default, continue, case

22.2 Un programma leggermente piu' complesso

Vediamo un programma piu' complesso che contiene alcune istruzioni in linguaggio C:

#include <stdio.h>

/* Questo programma stampa la tabella di conversione Fahrenheit-Celsius (dove fahr 'avanza' di 20 in 20) */ main ()

{

int fahr, celsius; int lower, upper, step; lower = 0; upper = 300; step = 20; fahr = lower; while (fahr <= upper)

{

celsius = 5 * (fahr-32) / 9; printf ("%d\t%d\n", fahr, celsius); fahr = fahr + step;

}

}

Guida Linux: il linguaggio C

La prima riga non ha bisogno di spiegazioni: la solita direttiva per il precompilatore. La seconda e terza riga contengono i caratteri /* e */: sono i delimitatori dei commenti. Qualsiasi cosa venga scritta all'interno di questi caratteri viene ignorata dal compilatore. Cio' e' molto utile se si vogliono inserire delle note senza che queste vengano lette dal compilatore e considerate delle istruzioni. Non e' possibile annidare piu' commenti uno dentro l'altro: e' solo possibile iniziare un commento con i caratteri /* e terminarlo con i caratteri */. L'indentazione (l'allineamento di cio' che si scrive) e' libero, percio' si puo' posizionare il testo come lo si ritiene piu' opportuno. Le prime due righe dopo la prima parentesi graffa sono delle dichiarazioni di variabili. In particolare in questo esempio sono state dichiarate le variabili fahr, celsius, lower, upper e step. Sono tutte variabili di tipo int. Le righe successive sono delle assegnazioni o istruzioni di assegnamento. Tramite queste assegnazioni le variabili lower, upper e step vengono inizializzate rispettivamente con i valori 0, 300 e 20. Inizializzare significa impostare un valore iniziale ad una variabile. L'istruzione fahr = lower e' anch'essa una istruzione di assegnamento in quanto viene assegnata alla variabile fahr il valore contenuto nella variabile lower. Poiche' lower e' stata inizializzata con 0, contiene il valore 0, quindi questa istruzione assegna 0 alla variabile fahr. E' possibile assegnare 0 a tutte e due le variabili con una sola istruzione:

fahr=lower=0;

E anche possibile dichiarare tutte le variabili con una sola istruzione:

int fahr, celsius, lower, upper, step;

E' importante ricordarsi di chiudere qualsiasi istruzione (anche quelle di dichiarazione e assegnamento)

con un punto e virgola. E' possibile dichiarare piu' variabili con una sola istruzione purche'

appartengano allo stesso tipo. In questo caso si tratta di 5 variabili dello stesso tipo (int) percio' si puo' usare una sola istruzione. In C ogni operazione e' composta da operandi ed operatori: nelle operazioni

di assegnamento l'operatore e' il carattere '='. In gergo informatico le strutture delle istruzioni di un

qualsiasi linguaggio vengono definite costrutti percio' questo sara' il termine usato qui da questo punto in poi. In questo programma vediamo un importante costrutto di iterazione: il while. L'istruzione while e' un costrutto di controllo del flusso del programma che permette di eseguire una o piu' istruzioni piu' volte. Il costrutto while funziona in questo modo: viene valutata la condizione all'interno delle parentesi tonde e se tale condizione e' vera, vengono eseguite le istruzioni contenute all'interno delle parentesi graffe. Una volta eseguite tali istruzioni, viene valutata nuovamente la condizione tra

parentesi tonde e se e' vera vengono ripetute le istruzioni all'interno delle parentesi graffe. Queste operazioni vengono ripetute all'infinito e terminano solo quando la condizione tra le parentesi tonde non e' piu' vera. Questo ripetersi di istruzioni viene definito loop o iterazione o ciclo. In altre parole, in questo programma vengono eseguite le istruzioni contenute all'interno del while mentre la condizione

e' vera (while significa appunto 'mentre'). Fino a quando la variabile fahr sara' minore o uguale alla

variabile upper verra' ripetuto il ciclo. Poiche' all'interno del corpo del while viene incrementata la

variabile fahr di 20 in 20, quando tale variabile sara' maggiore di 300 il ciclo terminera'.Infatti all'interno del ciclo troviamo l'istruzione:

fahr = fahr + step

tale istruzione assegna alla variabile fahr (attraverso l'operatore '=') il valore di fahr stessa incrementato con il valore della variabile step. In pratica la variabile fahr contiene inizialmente 0, al ciclo successivo conterra' 20, al ciclo successivo ancora 40, poi 60, 80 e cosi' via. Quando fahr sara' maggiore di 300 il ciclo terminera'. Se per errore viene impostata male la condizione tra parentesi tonde, e' possibile creare un cosidetto ciclo infinito. Cio' si verifica quando la condizione del while non puo' mai essere falsa. E' importante osservare che quando viene valutata una condizione, cio' che si fa e' in realta' tradurre una espressione condizionale in una espressione numerica. In altre parole l'espressione (fahr <= upper) viene trasformata in un numero. Se tale numero e' 0 la condizione e' falsa, in caso contrario la condizione e' vera. Inizialmente fahr contiene 0 e upper contiene 300:

poiche' la condizione 0 <= 300 e' vera, il risultato della valutazione di tale condizione e' diverso da 0. Quando fahr conterra' 320, la condizione 320 <= 300 sara' falsa ed il risultato sara' 0. A questo punto

il ciclo di while avra' termine. Alla luce di cio' e' evidente che un ciclo:

while (0)

sara' sempre falso, mentre un ciclo:

while (1)

sara' sempre vero. Quest'ultimo in particolare e' un caso di ciclo infinito. Le istruzioni contenute all'interno del corpo del while sono tutte epressioni matematiche abbastanza intuibili. Vale la regola delle espressioni matematiche secondo la quale vengono valutate per prime le espressioni contenute all'interno di parentesi tonde. Infine la funzione di libreria standard printf, ha un formato un po' diverso

Guida Linux: il linguaggio C

da quella usata nel primissimo programma visto inizialmente. Una funzione e' formata da un nome,

delle parentesi tonde e da qualcosa all'interno di tali parentesi. In realta' prima del nome della funzione possono esserci altre cose, ma cio' verra' illustrato in seguito. Quanto e' contenuto all'interno delle parentesi tonde viene definito argomento della funzione. Una funzione puo' accettare uno o piu' argomenti. La funzione printf e' abbastanza complessa in quanto puo' accettare vari argomenti con vari formati. Per ora e' sufficiente sapere che quanto e' contenuto all'interno degli apici doppi indica alla funzione come deve essere stampato un dato argomento. Quanto contenuto al di fuori degli apici doppi e' l'argomento da stampare. In questo programma dobbiamo stampare a video 2 argomenti:

fahr e celsius (la temperatura Fahrenheit e la temperatura in gradi centigradi, cioe' Celsius). Piu' precisamente tra i doppi apici troviamo: '%d', '\t' e '%d' nuovamente. Il primo '%d' significa che l'argomento da stampare e' un numero intero e si riferisce al secondo argomento (fahr). Il secondo '%d' significa la stessa cosa e si riferisce al terzo argomento (celsius). Per indicare che vengono forniti degli argomenti ad una funzione si dice in gergo che alla funzione vengono passati degli argomenti. A questa funzione printf vengono passati percio' 3 argomenti: "%d\t%d" e' il primo argomento, fahr e' il secondo e celsius e' il terzo. Il carattere costante '\t' e' una sequenza di escape ed indica il carattere di

controllo di tabulazione. In pratica viene inserita una tabulazione tra il primo ed il secondo argomento stampati a video. E' importante osservare infine l'ordine con il quale sono stati scritti gli operandi dell'espressione matematica: e' stata prima effettuata la moltiplicazione e soltanto dopo la divisione.

Se si fosse scritto diversamente, cioe' la divisione prima e la moltiplicazione dopo, si sarebbero ottenuti

solo valori nulli. Poiche' infatti la divisione di 5 con 9 produce come risultato circa 0,5, e poiche' stiamo

usando delle variabili intere (cioe' senza parti con la virgola) tutto quello che segue la virgola viene eliminato o meglio troncato. Per ottenere anche i valori dopo la virgola si sarebbero dovute usare delle variabili float, cioe' a virgola mobile.

22.3 Una variante del programma di conversione Fahrenheit-Celsius

Lo stesso programma di conversione delle temperature Fahrenheit-Celsius puo' essere scritto in molti

modi diversi. In sostanza cio' che deve fare questo programma e' sintetizzabile dall'espressione:

Celsius = 5 * (Fahrenheit - 32) / 9. Un modo diverso di codificare questa espressione lo si puo' osservare nel seguente listato:

#include <stdio.h>

#define

LOWER 0

/* limite inferiore della tabella */

#define

UPPER 300

/* limite superiore */

#define

STEP

20

/* incremento */

/* stampa la tabella Fahrenheit-Celsius */ main ()

{

 

float

fahr;

for

(fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) printf ("%3.0f %6.1f\n", fahr, (5 / 9.0) * (fahr - 32) );

}

questo programma produce lo stesso risultato del programma precedente, ma ha al suo interno alcune

differenze. Prima di tutto osserviamo 3 direttive '#define'; la variabile fahr e' stato dichiarata di tipo float anziche' int; il costrutto while e' stato sostituito con il costrutto for; il formato della funzione printf e' leggermente cambiato. Il programma e' stato volutamente scritto con alcune imperfezioni dal punto

di vista dello stile di codifica (stile spesso raccomandato da piu' fonti e tratto dagli esempi del

'linguaggio C' di Kernighan/Ritchie) per trovare lo spunto per alcune osservazioni. Per quanto riguarda lo stile di codifica osserviamo che le costanti '0', '300' e '20' non sono ripetute qua e la' nel codice del programma (hard coding) ma sono state usate alcune espressioni costanti in testa al programma (grazie alla direttiva '#define'). Inoltre sono state scritte in maiuscolo. Il C e' un linguaggio 'case sensitive', cioe' sensibile alla dimensione dei caratteri, quindi la costante 'a' e' diversa ovviamente dalla costante 'A' ma anche la variabile 'miavar' e' diversa dalla variabile 'MIAVAR'. Ad ogni modo scrivere i nomi delle variabili in minuscolo e i nomi delle costanti in maiuscolo e' una prassi ormai ampiamente diffusa e consigliata da varie fonti. Le istruzioni all'interno della funzione main() sono indentate di 4 caratteri, cioe' il carattere di tabulazione e' stato impostato a 4 caratteri. Alcune ricerche evidenziano che una spaziatura di 4 caratteri rende il programma molto piu' leggibile. Tra l'istruzione di dichiarazione della variabile fahr ed il costrutto for e' stata inserita una riga vuota. Anche questo rende il codice piu' leggibile. E' buona norma raggruppare le istruzioni per blocchi logici e suddividere i vari blocchi da una riga vuota. Se si abusa con queste tecniche (usando ad esempio piu' righe vuote) si rischia pero' di produrre l'effetto opposto: il codice sara' troppo 'diluito' e quindi meno leggibile. Prima della funzione main() e' stato inserito un commento che indica brevemente lo scopo della funzione. In rete esistono vari documenti di stile di codifica in C: un documento interessante lo si puo' trovare al

Guida Linux: il linguaggio C

sito del SEL (Software Engineering

Laboratory) della NASA (National Aeronautics and Space

Administration)

all'indirizzo

scegliendo

alla

voce

'documenti

online'

il

documento

'C

Style

Guide'.

Attualmente

l'indirizzo

completo

e':

http://sel.gsfc.nasa.gov/website/documents/online-doc/94-003.pdf. Terminate le osservazioni sullo stile di codifica, osserviamo le reali differenze rispetto al programma precedente. La variabile fahr e' stata dichiarata di tipo float in quanto la divisione '5 / 9' produce delle cifre con decimali. Usando il tipo int il risultato della divisione verrebbe troncato eliminando le cifre dopo la virgola, producendo dei valori poco precisi. Nella divisione sono state usate volutamente una costante intera ed una decimale per evidenziare una regola generale: se all'interno di una espressione aritmetica sono presenti degli interi, l'operazione eseguita e' intera. Se pero' un operatore ha un operando intero ed uno decimale, prima di eseguire l'operazione l'intero viene convertito in decimale. Percio', poiche la divisione '5 / 9.0' e' un operatore ('/') che ha l'operando di sinistra intero (5) e quello di destra decimale (9.0) il risultato sara' decimale. Per maggiore leggibilita' sarebbe stato meglio scrivere: '5.0 / 9.0' (l'esempio tratto dal K.R. in effetti lo fa). Nella funzione printf() il formato dell'output e' stato cambiato in '%3.0f %6.1f\n'. In questa stringa 'f' indica alla funzione che l'output e' di tipo frazionario e che la variabile

fahr deve essere stampata in un campo lungo almeno 3 caratteri senza punto decimale e senza cifre frazionarie. L'espressione %6.1f indica invece che il secondo numero deve essere stampato in un campo di almeno 6 caratteri con una cifra dopo il punto decimale. '\n' e' la solita sequenza di escape per produrre a video il carattere di newline (i risultati verrano stampati su righe diverse). Il costrutto for e' composto da 3 blocchi logici facoltativi: il primo blocco solitamente inizializza una variabile, il secondo blocco e' una condizione di controllo del ciclo ed il terzo blocco e' una espressione di incremento della variabile. Il primo blocco viene eseguito una sola volta prima di entrare nel ciclo. Il secondo blocco e' una condizione che viene valutata: solo se la condizione e' vera viene eseguito il corpo del ciclo. Una volta eseguito il ciclo viene eseguito il terzo blocco. Infine viene valutata nuovamente la condizione che se risulta ancora vera consente l'esecuzione del ciclo nuovamente. In altre parole il ciclo viene reiterato infinite volte fino a quando la condizione risulta vera e termina quando la condizione risulta falsa. Ad ogni iterazione del ciclo viene eseguito il blocco di incremento. L'inizializzazione, la condizione e l'incremento possono essere espressioni di qualsiasi tipo. Questi

blocchi logici sono facoltativi: e' possibile ad esempio scrivere 'for (;;);' che e' un ciclo infinito, infatti omettendo il blocco relativo al test della condizione, il compilatore assume che tale condizione sia sempre vera. Anche la funzione printf() puo' contenere espressioni di qualsiasi tipo, infatti la variabile celsius e' stata eliminata in quest'ultima versione del programma e sostituita dall'espressione '(5 / 9.0)

* (fahr - 32)' all'interno della funzione printf().

22.4 Ancora qualcosa sui float

Una costante numerica contenente un punto (ricordiamo che il punto rappresenta la parte non intera di una cifra con la virgola) e' per definizione considerata non intera. Alla luce di cio', il programma che segue terminera' con un classico errore di overflow:

#include <stdio.h>

int main(void)

{

double f = 5000000000.0; double g = 5000000000; printf("f=%lf\n", f); printf("g=%lf\n", g); return 0;

/* '.0' rende questa costante di tipo DOUBLE, cioe' non intero */ /* in assenza di '.0' questa costante e' un numero INTERO !!! */

}

Nella prima assegnazione, la costante numerica 5000000000.0 viene assegnata alla variabile di tipo double f. Fin qui tutto legale in quanto la costante 5000000000.0 non e' di tipo intero ma double. Nella seconda assegnazione iniziano i problemi: nella costante numerica 5000000000 non e' presente il punto e lo zero finali ('.0') rendendo implicitamente tale costante di tipo intero e non a virgola mobile. L'intero piu' grande e' il long int che se segnato (il default) puo' contenere al massimo il valore +2.147.483.647. Ma 5000000000 e' molto piu' grande di 2.147.483.647! Percio' la costante numerica 5000000000 semplicemente non puo' essere espressa come costante di tipo intero e verra' prodotto un errore di overflow (superamento del limite massimo esprimibile). In realta' non e' esattamente cosi', in quanto a partire dallo standard ISO/IEC 9899:1999 (c99) e' stato introdotto il tipo 'long long' (non supportato pero' dal vecchio standard c89) che puo' esprimere fino al valore massimo

+9223372036854775807, cioe' 2 63 - 1 se segnato (il default). Cio' significa che i compilatori aderenti al nuovo standard non avranno problemi con il programma precedente, ma i vecchi compilatori aderenti allo standard c89 potrebbero essere fonte di errori. Inoltre il tipo long long int introdotto dallo standard c99 e' un tipo a 64 bit (i valori esprimibili vanno da -(2^63 - 1) a 2^63 - 1) ma su molte macchine i registri sono ancora a 32 bit; ne consegue che occorrono due accessi in memoria al posto

di uno solo: cio' puo' rendere il codice piu' lento.

Guida Linux: il linguaggio C

Inizio della guida Il linguaggio C Indice Input ed Output

Copyright (c) 2002-2003 Maurizio Silvestri