Questo argomento è stato affrontato abbastanza superficialmente durante il corso base del linguaggio C con il Raspberry Pi, proprio per dare modo, ai principianti, di comprenderne la filosofia principale. In questa lezione approfondiremo, ancora di più, i concetti fondamentali sulle variabili e sul loro utilizzo. Vedremo, anche, il loro comportamento in termini di occupazione di spazio nella memoria RAM e le tecniche per utilizzarle al meglio. La presenza di abbondante codice dovrebbe favorire proprio il processo di acquisizione dei vari concetti.
Introduzione
Ricordiamo, ancora una volta, che una variabile è un nome attribuito a una piccola area di memoria, nella quale si può allocare un valore che il programma può manipolare. Nel linguaggio C esistono diversi tipi di variabili, differenti tra loro per la capienza, per l'intervallo di valori memorizzabili e per la varietà dei dati da immagazzinare. Il nome di una variabile può essere composto da lettere, numeri e dal carattere di "underscore". Esso non può iniziare per un numero e c'è differenza tra i caratteri maiuscoli e minuscoli. Vediamo qualche esempio:
- int k; (corretto);
- int cognome_e_nome; (corretto);
- int Cognome-Nome (sbagliato);
- int COGNOME_e_Nome; (corretto);
- int area12; (corretto);
- int 12area; (sbagliato).
Vari tipi di variabili
Esploreremo, adesso, le varie tipologie di variabili che, vedremo, sono molte più numerose di quelle esaminate durante i precedenti corsi sul linguaggio C erogati in queste pagine. Utilizzeremo, altresì, la funzione sizeof() allo scopo di determinare lo spazio occupato in memoria. La seguente tabella fornisce una panoramica generale sulle tipologie di variabili, il loro contenuto e la relativa capienza e può risultare utile per una rapida consultazione.
Tipo variabile | Occupazione bytes e bits |
Intervallo unsigned | Inervallo signed |
char | 1 (8) | da 0 a 255 | da -128 a +127 |
short int | 2 (16) | da 0 a 65535 | da - 32768 a +32767 |
int | 4 (32) | da 0 a 4294967295 | da -2147483648 a +2147483647 |
long int | 4 (32) | da 0 a 4294967295 | da -2147483648 a +2147483647 |
long long int | 8 (64) | da 0 a 18446744073709551615 | da -9223372036854775808 a +9223372036854775807 |
float | 4 (32) | +/- 3.4e +/- 38 (circa 7 digits) | |
double | 8 (64) | +/- 1.7e +/- 308 (circa 15 digits) | |
long double | 16 (128) | +/- 1.7e +/- 308 (~15 digits) |
Variabili di tipo char
Una variabile di tipo char rappresenta un dato di tipo intero (ossia senza cifre decimali), che occupa un solo byte (8 bit) in memoria RAM. La seguente codifica testimonia tale fatto.
#include <stdio.h> int main() { char valore; printf("Spazio occupato in bytes: %d\n",sizeof(valore)); return 0; }
che produce il seguente risultato:
Spazio occupato in bytes: 1
E' possibile anteporre il modificatore signed o unsigned in modo da poter gestire i seguenti intervalli di valore:
- unsigned char: da 0 a 255 (ossia da 00000000b a 11111111b);
- signed char: da -128 a +127 (ossia da 10000000b a 01111111b).
Pertanto, un errore di scelta di dominio ne potrebbe produrre un altro in visualizzazione, sebbene il tipo di variabile sia corretto. Ad esempio, il seguente codice:
#include <stdio.h> int main() { unsigned char valore; valore = -5; printf("Contenuto della variabile: %d. Spazio occupato in bytes: %d\n",valore,sizeof(valore)); return 0; }
produce, in output il risultato:
Contenuto della variabile: 251. Spazio occupato in bytes: 1
sebbene il valore -5 sia stato giustamente assegnato alla variabile. Per ottenere il corretto risultato occorre utilizzare il segnaposto "signed". Il programmatore deve, dunque, essere molto sensibile alla scelta oculata dei tipi di variabili ed è buona norma prevedere, in largo anticipo, il futuro contenuto.
Un'assegnazione di un valore decimale alla variabile di tipo char non produce alcun errore in fase di compilazione, ma i calcoli e le visualizzazioni risultano, ovviamente, troncate e limitate solo alla parte intera. Ad esempio, nel seguente codice:
#include <stdio.h> int main() { unsigned char valore; valore = 44.7; valore = valore * 2 + 1; printf("Contenuto della variabile: %d. Spazio occupato in bytes: %d\n",valore,sizeof(valore)); return 0; }
si otterrà, in uscita, il seguente messaggio:
Contenuto della variabile: 89. Spazio occupato in bytes: 1
anziché il corretto risultato matematico di 90.4.
Anche il verificarsi di possibili overflow possono mettere in crisi i programmatori più smaliziati. Il seguente codice raddoppia il valore di una variabile definita, volutamente per l'esempio, di tipo char:
#include <stdio.h> int main() { unsigned char valore; valore = 200 * 2; printf("Contenuto della variabile: %d. Spazio occupato in bytes: %d\n",valore,sizeof(valore)); return 0; }
Il risultato aspettato (e corretto) sarebbe 400, ma dal momento che le informazioni "traboccano" dalla variabile, il risultato visualizzato è il seguente:
Contenuto della variabile: 144. Spazio occupato in bytes: 1
Per fortuna, la maggior parte delle volte, il compilatore controlla molto bene il codice per cui, in questo caso, il programmatore riceverebbe un messaggio di warning di cui in figura 1, che lo metterà subito in guardia del pericolo.
Purtroppo, il compilatore non è sempre in grado di controllare eventuali condizioni di overflow. Esso, infatti, non esegue preliminarmente il codice ma si limita a un'analisi delle sintassi e delle operazioni matematiche esplicite. Nel successivo listato la compilazione avviene correttamente, senza alcun avvertimento da parte del compilatore, ma in esecuzione avviene il traboccamento della variabile al quarto incremento, secondo la sequenza dei risultati 80, 160, 240, 64 (e non, come si ci potrebbe aspettare, 320).
#include <stdio.h> int main() { unsigned char valore; valore = 0; valore = valore + 80; valore = valore + 80; valore = valore + 80; valore = valore + 80; printf("Contenuto della variabile: %d. Spazio occupato in bytes: %d\n",valore,sizeof(valore)); return 0; }
L'output finale, infatti è il seguente:
Contenuto della variabile: 64. Spazio occupato in bytes: 1
Il seguente altro codice è estremamente curioso e interessante. Esso focalizza il fatto di come un determinato valore possa essere espresso in diverse modalità, nell'esempio rispettivamente di tipo decimale, esadecimale, binario e carattere.
#include <stdio.h> int main() { unsigned char v1,v2,v3,v4; v1 = 65; v2 = 0x41; v3 = 0b1000001; v4 = 'A'; // Singoli apici printf("v1=%d, v2=%d, v3=%d, v4=%d\n",v1,v2,v3,v4); return 0; }
Il risultato della sua esecuzione è la seguente:
v1=65, v2=65, v3=65, v4=65
Variabili di tipo int
Rappresentano delle variabili intere la cui dimensione è quella più naturale per il processore ospitante, in termini di occupazione di memoria nei registri della CPU. La loro capienza potrebbe variare da CPU a CPU. Potrebbero occupare 2, 4 oppure 8 bytes. Esse dipendono dalla macchina su cui si sta lavorando e compilando. Il seguente listato dichiara una variabile di tipo int, le assegna un valore e, infine, visualizza il numero di byte che essa occupa in RAM, assieme al suo valore.
#include <stdio.h> int main() { int valore; valore = 1234; printf("Contenuto della variabile: %d. Spazio occupato in bytes: %d\n",valore,sizeof(valore)); return 0; }
Il risultato mostrato a video è il seguente:
Contenuto della variabile: 1234. Spazio occupato in bytes: 4
[...]
ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 2389 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici che potrai leggere in formato PDF per un anno. ABBONATI ORA, è semplice e sicuro.
Un linguaggio di programmazione è tanto più potente quanto il numero di tipi di variabili gestite. Ad ogni modo, qualunque tipo si tratti, esse sono solo sequenze di bytes in memoria. E’ il compilatore, poi, a “presentarle” in modo diverse.
Questo vuol dire che anche il programmatore può crearsi e inventarsi le sue variabili personalizzate.