Dopo essere stati chiusi per l’incendio del server che ci ospitava, grazie all’impegno di chi gestisce e mantiene questo sito, rieccoci qui con la prima lezione vera e propria del linguaggio C.
Oggi vedremo i primi concetti su cui si basa la programmazione C mediante un piccolo sorgente che chiameremo esempio1_1.c. (per compilarlo o usate lo script riportato nella Lezione 0 o semplicemente eseguite
gcc esempio1_1.c -o esempio1_1
1 2 3 4 5 6 7 8 9 10 |
#include <stdio.h> int main() { int a; a = 100; printf("La variabile a è uguale a: %d\n",a); return 0; } |
La prima caratteristica che osserviamo è la presenza alla linea 2 di una funzione main(). Questa è l’inizio di ogni programma C e non può mancare (causerebbe un errore di compilazione). Questa come tutte le funzioni C inizia con la parentesi graffa aperta e si chiude con la parentesi graffa chiusa {}. Nella lezione 0 è riportato come ottenerle con la tastiera italiana (assieme alle quadre []).
Altro elemento caratteristico è che tutte le istruzioni contenute nella funzione terminano con il punto e virgola (;)
Rispetto ad altri linguaggi che non prevedono un terminatore di istruzione, qui è sempre necessario terminare le istruzioni.
In linea 4 osserviamo la dichiarazione di una variabile. Molti linguaggi non prevedono questa prassi, come per esempio il Python che decide da solo il tipo di dato che vogliamo utilizzare. In C questo non lo si può fare: tutte le variabili che vogliamo usare, prima devono essere dichiarate. Essenzialmente dobbiamo avvertire il compilatore che nel corso del programma abbiamo intenzione di utilizzare una variabile intera chiamata a.
Nel corso vedremo tutti i tipi presenti nel C (o almeno i più utilizzati), per il momento abbiamo introdotto il tipo intero che si rappresenta con la parola riservata int .
Nel passato era prassi dichiarare tutte le variabili impiegate nel programma, all’inizio ed in alcuni casi, violando questa regola, il compilatore segnalava un errore. Ultimamente questa rigidità è stata rilassata consentendo di mettere le dichiarazioni ove si vuole nel codice sorgente. Sono allora nate due scuole di pensiero:
- C’è chi dice che mettendo tutte le dichiarazioni in testa al sorgente, sia più facile gestire le variabili ed evitare di dimenticare di dichiararne qualcuna. Inconveniente, bisogna sempre fare su e giù nel codice per essere sicuri di aver dichiarato la variabile, il suo nome, il suo tipo.
- Altri invece preferiscono dichiarare le variabile subito prima di essere utilizzate. Si evita l’inconveniente del caso uno ma in alcuni casi potrebbe accadere di dimenticare la dichiarazione e, inconveniente più rilevante, se riutilizzerete successivamente la variabile ed avrete dei dubbi, di sicuro la sua dichiarazione non la troverete all’inizio del codice ma dovete cercarla.
Solo l’esperienza potrà dirvi quale metodologia sia più adatta a voi.
Altra caratteristica di questo linguaggio è il case sesitive. In italiano potremmo tradurlo con che tiene conto del maiuscolo o minuscolo. Le tre variabili Pippo, PippO e PIPPO, sono tre grandezze distinte e differenti. Molto importante allora quando si dichiara una variabile, riutilizzarla così come è stata dichiarata altrimenti si ottiene un errore (da questo motivo dipende il su e giù nel codice per essere sicuri di utilizzare il nome corretto).
Come consiglio, ci viene da dire che è sempre bene scegliere nomi di variabili significativi. Se dovete rappresentare l’altezza di una scala, un generico ed anonimo h, effettivamente fa pensare ad un’altezza ma se domani ne introdurrete un’altra potreste fare confusone. il nome altezzaScala invece è molto più chiaro ed auto esplicativo. Qui è stata utilizzata la notazione “a Cammello”. Più parole che esprimono il significato della variabile vengono unite evidenziando l’inizio della nuova parola con la maiuscola. Uso comune è iniziare le variabili con lettera minuscola e le funzioni da noi definite (che vedremo in seguito) con lettera maiuscola.
Alla riga 6 stiamo attribuendo ad a un valore ossia la stiamo valorizzando. Ricordiamo che quando un programma inizia a girare, una variabile potrà contenere qualsiasi valore, non necessariamente 0 come potrebbe essere ragionevole pensare. Quindi è sempre bene inizializzare una variabile prima di usarla.
Alla riga 7 abbiamo usato una funzione che il C ci mette a disposizione. Come per le variabili, è necessario informare il compilatore anche delle funzioni che si vogliono utilizzare. Per quelle che scriveremo noi, dovremo farne una dichiarazione esplicita mentre per quelle che ci mette a disposizione il linguaggio, possiamo servirci degli header.
Alla riga 1 infatti è presente una così detta “direttiva per il Preprocessore”. Prima di iniziare la fase vera e propria di compilazione, c’è una fase in cui il così detto Preprocessore modifica il codice sulla base delle nostre direttive.
In questo caso si sta chiedendo di includere in testa a tutto il programma un file (stdio.h) che contiene tutte le dichiarazioni delle funzioni di “standard input e output” tra cui sarà presente anche la printf. Il fatto di aver racchiuso il nome del file header tra i simboli <> indica al preprocessore di andare a cercare questo file nelle posizioni della macchina che sono universalmente definite (standard) per contenere questi file. Se invece il .h è stato generato da noi, per indicare al preprocessore dove trovarlo, si usano le “”.
Se siete curiosi di vedere come sarà trasformato il codice originale dopo il lavoro del preprocessore, eseguite il seguente comando
gcc -E esempio1_1.c -o esempio1_1.pp.c
il file esempio1_1.pp.c conterrà tutta una serie di dichiarazioni ed istruzioni che ancora non abbiamo visto, ma di sicuro ci sarà anche la dichiarazione di printf.
Supponiamo adesso che per risolvere un problema di programmazione dobbiamo usare la funzione FunzionePerNoi, come abbiamo saputo da un amico o trovato in un blog di internet. Sorge allora il problema di come usarla e quale .h includere nel nostro sorgente.
In queste necessità ci viene incontro l’istruzione Linux man. Apriamo un terminate ed eseguiamo il comando (usiamo per esempio printf)
man printf
Nel 90% dei casi, si aprirà la pagina di nostro interesse. In questo caso però appare una pagina la cui prima riga è:
PRINTF(1) User Command PRINTF(1)
Siamo quindi capitati in un comando di shell e non nella nostra istruzione che stavamo cercando.
Questo è dovuto a come sono organizzati i manuali in Linux, ossia:
- 1 – Programmi eseguibili o comandi di shell
- 2 – Funzioni che chiamano il sistema (messe a disposizione dal kernel Linux)
- 3 – Funzioni appartenenti a librerie di programmazione
- 4 – Giochi
- 5 – ….
- ……
e quindi quando richiamiamo una funzione, se c’è omonimia, viene restituito il primo titolo trovato, in questo caso il printf che si può usare nella shell Linux.
Per risolvere il problema basta eseguire
man 3 printf
e nella prima riga si troverà:
PRINTF(1) Linux Programmer's Manual PRINTF(1)
e tra le prime righe c’è riportato che si deve includere stdio.h .
E’ stato detto che questi casi di omonimia sono abbastanza rari. Per esempio cercando la funzione sprintf (simile alla printf ma scrive nelle stringhe) si ottiene direttamente la pagina relativa alla libreria di programmazione.
Alcuni casi di utilizzo della printef li vederemo nelle prossime lezioni.
Relativamente al codice esempio1_1.c, abbiamo esaminato quasi tutto. Quello che ci manca è l’int presente alla riga 2 ed il return 0; presente alla riga 9.
Come detto precedentemente, la funzione main è molto particolare e deve sempre essere presente in un codice C.
Dalla riga 2 si vede che questa funzione restituisce una grandezza intera, ma a chi? La restituisce al sistema operativo. Vediamo a cosa può servire questo valore e come leggerlo.
Un codice, per svariati motivi, anche se sintatticamente corretto, potrebbe comunque incorrere in una condizione di errore. A titolo di esempio si può citare la mancanza di un file di dati, un ingresso inaspettato, memoria insufficiente per una ben precisa operazione etc.
In generale, se si è così bravi da gestire tutte le possibili condizioni di errore ed evitare che il codice, come si suol dire faccia “crash“, senza riportare alcun messaggio, sarà sufficiente fare una scritta a video riportando l’errore ed indicando possibili soluzioni del problema. Questo però se abbiamo un video!!!
Cosa accade invece nel caso di un sistema embedded (o micro controllore) che magari fa partire il programma quando si accende la macchina? Codificando opportunamente queste condizioni di errore e restituendo tale valore al sistema operativo, sarà comunque possibile capire cosa sia successo.
Il comando Linux che stampa tale valore è:
echo $?
Di seguito proviamo a riassumere i concetti riportati in questa lezione
Riassunto
- In un codice C deve essere sempre presente la funzione main()
- Ogni istruzione deve sempre essere terminata con punto e virgola
- Questo linguaggio è case sensitive
- Prima di utilizzare una variabile bisogna dichiararla
- Prima di utilizzare una variabile dovrebbe essere inizializzata
- Prima di utilizzare una funzione bisogna dichiararla
- Per utilizzare le funzioni di libreria bisogna dichiararle tramite i file header
- La variabile di uscita del main può essere utilizzata da sistemi embedded e micro controllori per valutare un eventuale errore.
Esercizi
Provate ad usare il codice fornito all’inizio ed a violare alcune delle regole riportate e verificate i messaggi di errore del compilatore
- Togliere una virgola
- Usate una variabile senza dichiararla
- Eliminare l’istruzione include (la stampa avviene lo stesso?)
- Cambiare il valore restituito dal main e rilevarlo
E con questo la lezione è finita.
Alla prossima!
PS. Al fine di distribuire i sorgenti che appariranno nel corso, è stato inizializzato un repository remoto su Git Hub. Quando si impara un linguaggio è sempre meglio scrivere i sorgenti piuttosto che copiarli, per prendere confidenza con la sintassi e le parole chiave. E’ anche vero però che può capitare che copiando, magari per inesperienza, si faccino degli errori senza accorgersene e non si riesca a compilarlo. Si può allora ricorrere a questi file e vedere le differenze. Per ottenere allora la vostra copia del repository remoto seguite le seguenti istruzioni:
- Verificate se sulla vostra Raspberry git è già installato (eseguendo il comando git verrà stampato l’help). Se non c’è installatelo con il solito sudo apt-get install git.
- Per copiare i sorgenti dovete solo eseguire:
- git clone https://github.com/arkkimede/Sorgenti_CorsoC_RPI.git
- Otterrete così la directory Sorgenti_Corso_RPI con sotto directory del tipo Lezione_00, Lezione_01 etc., contenenti i sorgenti della relativa lezione.
- Ogni tanto ritornate nella direcory Sorgenti_Corso_RPI ed eseguite il comando git pull ogni modifica del repository on line verrà apportata sulla vostra copia, ottenendone così una versione aggiornata.
Qui il forum di supporto al corso.
Se vuoi restare aggiornato, seguici anche sui nostri social: Facebook, Twitter, Youtube
Se vuoi anche trovare prodotti e accessori Raspberry Pi in offerta, seguici anche su Telegram !!