C/Variabili, operatori e costanti/Variabili
Una variabile in C (e in programmazione in generale) è uno spazio di memoria che contiene un valore. Una variabile ha un nome che viene usato nella programmazione per riferirsi a un certo particolare spazio di memoria.
Nozioni minime
modificaI tipi di dati elementari gestiti dal linguaggio C dipendono dall'architettura dell'elaboratore sottostante. In questo senso è difficile definire la dimensione delle variabili numeriche; si possono dare solo delle definizioni relative. Solitamente, il riferimento è dato dal tipo numerico intero (int) la cui dimensione in bit corrisponde a quella della parola, ovvero dalla capacità dell'unità aritmetico-logica del microprocessore.
I documenti che descrivono lo standard del linguaggio C, definiscono la «dimensione» di una variabile come rango.
Bit, byte e caratteri
modificaIl riferimento più importante in assoluto è il byte, che per il linguaggio C è almeno di 8 bit, ma potrebbe essere più grande. Dal punto di vista del linguaggio C, il byte è l'elemento più piccolo che si possa indirizzare nella memoria centrale, questo anche quando la memoria fosse organizzata effettivamente a parole di dimensione maggiore del byte. Per esempio, in un elaboratore che suddivide la memoria in blocchi da 36 bit, si potrebbero avere byte da 9, 12, 18 bit o addirittura 36 bit.
Una volta definito il byte, si considera che il linguaggio C rappresenti ogni variabile scalare come una sequenza continua di byte; pertanto, tutte le variabili scalari sono rappresentate come multipli di byte; di conseguenza anche le variabili strutturate lo sono, con la differenza che in tal caso potrebbero inserirsi dei «buchi» (in byte), dovuti alla necessità di allineare i dati in qualche modo.
Il tipo char (carattere), indifferentemente se si considera o meno il segno, rappresenta tradizionalmente una variabile numerica che occupa esattamente un byte.
Dichiarazione
modificaPer essere usata una variabile va prima dichiarata. La dichiarazione è la seguente:
tipo nome_variabile;
che dichiara una variabile il cui nome è nome_variabile
e il cui tipo è tipo
. Per esempio se vogliamo dichiarare una variabile di tipo intero possiamo farlo nel seguente modo:
int numero;
dove int
è un tipo di variabile usato per rappresentare i numeri interi.
È possibile dichiarare più variabili dello stesso tipo su una sola riga, per esempio:
int a, b;
dichiara due variabili di tipo int
.
Le variabili possono essere inizializzate durante la dichiarazione:
int c = 1;
se non si inizializza una variabile si dice che il suo valore è indeterminato, nel senso che non si può dire che cosa contenga. Tipicamente una variabile non inizializzata contiene garbage, spazzatura, cioè contiene quello che è presente nel suo indirizzo al momento dell'allocazione. Alcuni compilatori azzerano le variabili appena dichiarate. Poiché il comportamento del compilatore in genere non è noto, e poiché il nostro programma dovrà essere compatibile col maggior numero di compilatori, il programma dovrà inizializzare le variabili prima dell'uso. Se si dichiarano più variabili sulla stessa riga possono essere o meno inizializzate:
int d, e = 2;
che crea due variabili, ma solo la seconda è inizializzata
int f = 1, g = 2;
tutte e due sono inizializzate.
esiste anche la possibilità di effettuare una dichiarazione concatenata:
int a=b=3;
entrambe le variabili vengono inizializzate al valore 3
Nomi di variabili
modificaIl nome di una variabile viene scelto dal programmatore, e tecnicamente si chiama "identificatore". La scelta di un particolare nome non ha influenza sull'esecuzione del programma. Esistono, tuttavia, le seguenti regole:
- due variabili non possono avere lo stesso nome
- si possono usare sia le lettere che i numeri, ma il primo carattere deve essere una lettera
- anche se non è errore è meglio non usare l'underscore _ come iniziale per evitare conflitti con le librerie che spesso usano variabili che iniziano con l'underscore
- il C è case sensitive, quindi le maiuscole sono distinte dalle minuscole
- il numero di caratteri significativi è di almeno 31 caratteri (63 per l'ultimo standard) per i nomi interni, e 6 caratteri (31 per l'ultimo standard) per quelli esterni
- le parole riservate come
if
,int
, ... non possono essere usate per i nomi delle variabili
Il numero di caratteri significativi indica quanti caratteri iniziali, nel nome, possono essere uguali, prima che intervenga un conflitto di similitudine
int abcdefghijklmnopqrstuvwxyzABCDEaaa;
int abcdefghijklmnopqrstuvwxyzABCDEbbb; /* Possibile conflitto con la precedente! */
Nel caso sopra vengono definite due variabili, i cui primi 31 caratteri del nome sono uguali: questo può impedire al compilatore di distinguerle fra loro, generando un errore.
Da notare che se per i nomi interni (usati all'interno di un singolo modulo/file) i caratteri significativi sono ben 31, per quelli esterni (definiti in un modulo, ma usati da un altro modulo di programma) il limite è di soli 6 caratteri.
In realtà questo limite così basso è stato spesso esteso dalla quasi totalità dei compilatori. Il motivo più forte per attenersi a questa regola, così rigida, è quello della compatibilità del proprio codice verso vecchi sistemi o compilatori.
Assegnazione
modificaCon l'operatore di assegnazione =
si assegna ad una variabile un particolare valore:
int a;
a = 1;
la variabile a è dichiarata di tipo intero e successivamente gli viene assegnato il valore 1. Ad una variabile può essere assegnato il valore di un'altra variabile:
int b,c = 0;
b = c;
entrambe le variabili valgono 0.
L'assegnamento può essere fatto anche per più variabili contemporaneamente:
int x,y;
x = y = 2;
Ad entrambe le variabili viene assegnato il valore 2.
I tipi di variabili
modificaAd ogni variabile in C deve essere associato un tipo, quindi quando si dichiara una variabile bisogna specificare sempre di che tipo è. In C le variabili possono essere di 5 tipi base:
int
: sono i numeri interi (16 bit).float
: sono i numeri a virgola mobile (in inglese "floating-point") a precisione singola (32 bit).double
: sono i numeri a virgola mobile a precisione doppia (64 bit).char
: sono le variabili che contengono un carattere (8 bit).void
: il non-tipo: non si possono creare variabili void, ma questo tipo è usato per dichiarare puntatori generici (che possono puntare a qualunque tipo di dati) e per definire funzioni che non restituiscono alcun valore.
Le dimensioni reali del tipo int
, e che decidono quindi la gamma di numeri rappresentabili, dipendono strettamente dall'implementazione (compilatore e tipo di processore).
Le specifiche di linguaggio suggeriscono che abbia una dimensione naturale suggerita dall'ambiente di esecuzione; questo si traduce solitamente in almeno 16 bit (gamma numerica da -32768 a +32767), anche nella maggioranza dei processori/compilatori a 8 bit, ma con processori a 32 bit è piuttosto comune che il compilatore usi int
a 32 bit (gamma da -2147483648 a +2147483647).
In casi particolari anche le dimensioni del tipo char
sono state aumentate dal minimo degli 8 bit, come per alcuni DSP. In quel caso poteva essere giocoforza, date alcune ALU a soli 16 o 32 bit, costringere anche il tipo char
a 16 o 32 bit.
Per la certezza delle dimensioni rimane in ogni caso fondamentale la documentazione del compilatore, oltre che il file header limits.h
fornito; in questo file sono presenti delle definizioni con #define, come ad esempio INT_MIN
e INT_MAX
, ai limiti di gamma numerica rappresentabile.
Uno scopo analogo è dato al file header float.h
, per i numeri a rappresentazione in virgola mobile.
Esempi d'uso
modifica#include <stdio.h>
int main(void)
{
int numeroIntero = 45;
//int numeroIntero = 055; //45 in ottale
//int numeroIntero = 0x2D; //45 in esadecimale
float numeroInVirgolaMobile = 54.78;
double numeroInVirgolaMobileDoppio = 34.567;
char carattere = 'a';
printf("La variabile intera è uguale a %d", numeroIntero);
numeroIntero = 54;
numeroInVirgolaMobile = 78.9;
numeroInVirgolaMobileDoppio = 179.678;
carattere = 'b';
}
Opzioni
modificaÈ possibile utilizzare delle opzioni quando si dichiarano le variabili per ottenere speciali comportamenti.
const
modificaIl comando const
dichiara delle costanti, ovvero dei nomi assegnati in modo fisso a dei valori. In pratica sono come delle macro, solo che non accettano funzioni come valore.
Si può usare anche la direttiva #define
per ottenere lo stesso risultato, ma in generale l'uso di const
è preferibile.
Inoltre le variabili const
sono anche usate come parametri delle funzioni per assicurarsi che all'interno della funzione il valore di una variabile non possa subire variazioni.
const int dieci = 10;
printf("Il numero è %d", dieci);
Si possono anche dichiarare puntatori costanti. La cosa interessante è come varia il comportamento del puntatore a seconda che il modificatore const venga prima o dopo l'asterisco (*). Nella fattispecie se viene prima, il puntatore non potrà modificare l'area di memoria ma potrà spostarsi liberamente su di essa, se viene dopo potrà modificare l'area di memoria ma non potrà spostarsi, comportandosi come un array (dichiarato con le []).
volatile
modificaLa parola chiave volatile
indica al compilatore che il valore della corrispondente variabile potrebbe essere modificato da eventi esterni al normale flusso sequenziale di esecuzione delle istruzioni.
In condizioni normali un compilatore può ottimizzare una sequenza consecutiva di accessi in lettura ad una variabile privi di scritture intermedie producendo un codice eseguibile che ne prelevi il valore dalla memoria un'unica volta, collocandolo in uno dei registri del processore, e successivamente riutilizzare tale registro nella certezza che questo continui a contenere il valore corretto.
L'opzione volatile
elimina tale certezza, costringendo di fatto il compilatore a produrre codice che rilegge il valore della variabile dalla memoria per ogni accesso in lettura, non essendovi garanzia che questo sia, nel frattempo, rimasto inalterato.
volatile int laMiaVariabile=14;
static
modificaÈ una variabile che ha permanenza statica in memoria. Se si dichiara una variabile di tipo static all'interno di una funzione, essa manterrà il suo valore anche quando l'esecuzione della funzione sarà terminata. Se la stessa funzione fosse invocata un'altra volta, la variabile statica avrà ancora il valore presente alla precedente uscita della funzione.
Lo specificatore static
assume un secondo significato se la variabile è definita al livello base del file (fuori da qualsiasi funzione).
In quel caso, oltre a indicare che la sua esistenza in memoria è valida per tutta l'esecuzione del programma, indica anche che la sua visibilità è ristretta all'interno del modulo (file).
Nessun altro modulo di programma potrà farvi riferimento con lo specificatore extern
.
Questo permette di avere variabili con nomi uguali, specificate in moduli diversi, senza che possano avere conflitti fra loro: ciascuna sarà visibile solo nel proprio modulo.
auto
modificaLa classe di memorizzazione auto
indica che una variabile può essere assegnata in memoria in modo automatico dal compilatore. Questo tipo di memorizzazione ha senso solo all'interno di una funzione.
Il suo uso è decaduto rapidamente nel tempo, in quanto è il tipo di memorizzazione predefinito all'interno delle funzioni, per cui non necessita di essere specificato.
extern
modificaLa classe di memorizzazione extern
indica che una variabile è definita esternamente al modulo.
La definizione extern
ha lo scopo d'informare il compilatore che ogni riferimento alla variabile dovrà essere predisposto e infine risolto solo nella fase finale di linking.
extern int n; /* Uso interno al modulo, definizione esterna */
register
modificaLo specificatore register
indica che l'uso di una variabile è critico per il tempo di esecuzione del programma: il suo accesso dovrà quindi essere più veloce possibile, con la memorizzazione diretta in un registro della CPU o tramite altri metodi in grado di velocizzarne l'accesso. Questo comando potrebbe essere ignorato dal compilatore se non vi sono registri liberi nella CPU (nel contesto di esecuzione in cui la variabile è visibile), oppure se i registri liberi non sono di dimensioni sufficienti a contenere il tipo di dato.
register int i = 1;
for (i=1; i<=10; i++)
printf("Il numero è %d \n", i);
Va osservato che nei compilatori moderni, dove l'uso delle risorse di CPU è generalmente reso al meglio, l'uso di questo specificatore è spesso ridondante.
long
modificaLo specificatore long
può essere impiegato sia come modificatore di alcuni tipi base (aumentando lo spazio assegnato alla variabile), che direttamente come tipo di dati, applicandosi in modo predefinito al tipo int
.
Ad esempio, con
long int a;
long b;
le due variabili hanno le stesse dimensioni, come spazio di memorizzazione (fino al doppio delle dimensioni del tipo int
, potendo rappresentare quindi una gamma di valori più ampia).
La relazione fra variabili int
e long
è in realtà dipendente dall'implementazione del compilatore: su sistemi a 8/16 bit si possono avere casi in cui int
ha una rappresentazione a 16 bit e long
a 32 bit, mentre su sistemi a 32 bit non è infrequente trovare sia int
che long
a 32 bit.
Alcune implementazioni permettono la doppia specificazione long
, aumentando ancora la dimensione di alcuni tipi di dato, per esempio:
long long foo; /* Variabile a 64 bit */
short
modificaLo specificatore short
può essere impiegato sia come modificatore di alcuni tipi base (riducendo lo spazio assegnato alla variabile), che direttamente come tipo di dati, applicandosi in modo predefinito al tipo int.
Ad esempio, nel caso
short int a;
short b;
le due variabili hanno le stesse dimensioni, in modo analogo alle considerazioni per lo specificatore long
.
signed
modificaLo specificatore signed
indica che una data variabile va trattata con segno positivo o negativo, nei calcoli aritmetici. Può essere impiegato sia come modificatore di alcuni tipi base, che direttamente come tipo di dati, applicandosi in modo predefinito al tipo int
.
Ad esempio, nel caso
int a;
signed int b;
signed c;
le tre variabili hanno dimensioni e funzionalità equivalenti (il tipo int
viene già trattato con segno aritmetico).
unsigned
modificaLo specificatore unsigned
indica che una data variabile va trattata sempre con segno positivo, nei calcoli aritmetici. Può essere impiegato sia come modificatore di alcuni tipi base, che direttamente come tipo di dati, applicandosi in modo predefinito al tipo int
.
Ad esempio, nel caso
unsigned int a;
unsigned b;
le due variabili hanno dimensioni e funzionalità equivalenti.
Uno degli scopi più comuni del trattare variabili senza segno (ovvero sempre come valori positivi o zero) è quello di ampliare la gamma di valori nel campo positivo, per le quantità numeriche che non necessitano di rappresentazione negativa. Una variabile int
a 32 bit può spaziare numericamente da -2147483648 a +2147483647, mentre se dichiarata unsigned int
può andare da 0 a 4294967295.