Ogni variabile in C ha un tipo, scelto quando la dichiari e fisso per tutta la sua vita. Il tipo decide tre cose: quanti byte occupa la variabile, come vengono interpretati quei byte e quali operazioni hanno senso su di essa.
L'elenco dei tipi del C è breve. La complessità sta nei modificatori.
I quattro tipi base
charcontiene un byte. Si usa per singoli caratteri ('A'), per i byte di una stringa e a volte come intero minuscolo.intè il tipo intero da lavoro. Contatori di ciclo, dimensioni, ID.floatedoublecontengono numeri reali.doubleha circa il doppio della precisione.voidè in un certo senso il quarto tipo, ma significa "nessun valore": non puoi dichiarare una variabilevoid, solo usarlo come tipo di ritorno di una funzione, come lista di parametri vuota o come puntatore senza tipo.
Nota il suffisso f in 3.14f. Senza di esso il letterale è un double, e assegnarlo a un float perde precisione in silenzio. Alcuni compilatori avvisano.
Modificatori: short, long, unsigned
I tipi interi base hanno modificatori che ne cambiano la dimensione o il segno.
Ogni tipo intero ha bisogno del suo specificatore di formato: %hd per short, %d per int, %ld per long, %lld per long long, %u per unsigned. Usare quello sbagliato è comportamento indefinito, non un errore di arrotondamento.
unsigned elimina il bit di segno e raddoppia l'intervallo positivo. Un unsigned int contiene valori da 0 a circa 4,3 miliardi invece che da -2,1 a +2,1 miliardi. È la scelta giusta per le cose che davvero non possono essere negative, come dimensioni, conteggi di byte, schemi di bit, e una trappola per tutto ciò che potrebbe scendere sotto zero con una sottrazione.
signed è il default per int, short, long e long long, quindi non lo scrivi quasi mai. L'unico caso in cui conta è char: se un char semplice sia signed o unsigned è definito dall'implementazione, quindi scrivi signed char o unsigned char quando il segno conta.
Dimensioni: cosa ottieni davvero
Lo standard C specifica dei minimi, non dimensioni esatte. Su qualsiasi macchina moderna a 64 bit con Linux, macOS o Windows vedrai questo:
| Tipo | Dimensione tipica | Intervallo tipico |
|---|---|---|
char | 1 byte | da -128 a 127 (o da 0 a 255) |
short | 2 byte | da -32.768 a 32.767 |
int | 4 byte | da -2.147.483.648 a 2.147.483.647 |
long | 8 byte (4 su Windows) | circa ±9,2 trilioni |
long long | 8 byte | circa ±9,2 trilioni |
float | 4 byte | ~7 cifre significative |
double | 8 byte | ~15 cifre significative |
long double | 16 byte (variabile) | più di double |
La riga di long è quella che frega: vale 8 byte su Linux e macOS e 4 byte su Windows a 64 bit. Il codice che presume che long possa contenere un valore a 64 bit non è portabile. Usa long long, oppure i tipi a larghezza esatta di stdint.h (int32_t, uint64_t) quando la dimensione fa parte dei requisiti.
sizeof: chiedilo al compilatore
Non indovinare mai una dimensione: misurala:
sizeof è un operatore, non una funzione, e viene valutato in fase di compilazione. Restituisce un valore di tipo size_t, che si stampa con %zu.
sizeof(char) vale per garanzia esattamente 1: è la definizione di byte in C. Tutto il resto si misura rispetto a esso.
limits.h e float.h
Gli intervalli esatti per il tuo compilatore sono disponibili come costanti con un nome:
Sono i valori con cui confrontarti quando devi sapere se un'operazione andrà in overflow. Verificare if (a > INT_MAX - b) prima di calcolare a + b è il modo per rilevare l'overflow prima che accada, e questo conta, perché dopo non puoi rilevare l'overflow signed.
Overflow degli interi
Cosa succede quando un valore supera l'intervallo del suo tipo dipende interamente dal segno.
L'overflow unsigned è definito: il valore ricomincia modulo 2^N.
Il secondo caso è una fonte reale di bug. Un ciclo come for (unsigned i = n - 1; i >= 0; i--) non finisce mai, perché un valore unsigned è sempre >= 0.
L'overflow signed è comportamento indefinito. Non "ricomincia da capo": indefinito. Il compilatore può presumere che non accada mai e ottimizzare su questa base, il che significa che un controllo di overflow scritto dopo può essere eliminato:
int sum = a + b;
if (sum < a) { /* il compilatore può eliminarlo del tutto */ }
Controlla prima, usando i limiti:
if (b > 0 && a > INT_MAX - b) {
/* a + b andrebbe in overflow: gestiscilo */
}
Precisione della virgola mobile
float e double memorizzano i numeri in binario, e la maggior parte delle frazioni decimali non ha una forma binaria esatta, così come 1/3 non ha una forma decimale esatta.
La regola che ne segue: non confrontare mai valori in virgola mobile con ==. Confronta invece la differenza assoluta con una piccola tolleranza.
E non usare mai la virgola mobile per il denaro. Memorizza i centesimi come intero; un long long di centesimi è esatto dove un double di euro non lo è.
Scegliere un tipo
Una breve lista di decisioni che copre quasi tutto il codice:
- Numeri interi:
int, a meno che tu non abbia un motivo. È il tipo che la CPU gestisce in modo più efficiente e quello attorno a cui sono costruite tutte le regole aritmetiche. - Qualsiasi cosa oltre i 2 miliardi:
long long, oppureint64_tdistdint.h. - Dimensioni, lunghezze, indici di array che arrivano da
sizeofostrlen:size_t. È unsigned e garantito abbastanza grande per qualsiasi oggetto. - Decimali:
double. Usafloatsolo per dimezzare la memoria in array grandi o su hardware embedded senza unità a doppia precisione. - Singoli caratteri e byte grezzi:
charper il testo,unsigned charper i dati binari. - Vero/falso:
booldistdbool.h, vedi i booleani in C. - Larghezze in bit esatte (formati di file, protocolli di rete, registri hardware):
stdint.h, cioèuint8_t,int16_t,uint32_te così via.
Mescolare i tipi
Quando combini due tipi diversi in un'espressione, il C li converte alle tue spalle prima di fare i calcoli. Di solito è utile e ogni tanto è un disastro:
Il primo è una divisione intera: entrambi gli operandi sono int, quindi il risultato è un int e la parte frazionaria viene scartata. Il terzo è peggio: confrontare un valore signed con uno unsigned converte quello signed in unsigned, trasformando -1 in un enorme numero positivo.
Queste regole di conversione, e come prenderne il controllo con cast espliciti, sono l'argomento del type casting in C.
Domande frequenti
Quali sono i tipi di dati di base in C?
Quattro tipi base: char per singoli caratteri e byte, int per i numeri interi, float e double per i decimali. I modificatori ne cambiano dimensione e segno, short, long, long long, signed e unsigned, e producono l'insieme completo.
Quanti byte occupa un int in C?
Quasi sempre 4 byte (32 bit) sui sistemi desktop e server moderni, con un intervallo di circa da -2,1 miliardi a 2,1 miliardi. Lo standard garantisce solo almeno 2 byte, e i microcontrollori a 16 bit usano davvero 2 byte. Usa sizeof(int) se devi saperlo con certezza.
Qual è la differenza tra float e double in C?
float occupa 4 byte con circa 7 cifre decimali significative; double occupa 8 byte con circa 15. Double è il tipo predefinito per i letterali in virgola mobile e per le funzioni matematiche, e a meno che tu non stia memorizzando milioni di valori o lavorando su un chip embedded, double è la scelta giusta.
Cosa succede quando un int va in overflow in C?
Per un int signed, l'overflow è comportamento indefinito: il compilatore può far ricominciare il valore, saturarlo o eliminare del tutto il controllo in ottimizzazione. Per un int unsigned è completamente definito: il valore ricomincia modulo 2^N, quindi UINT_MAX + 1 vale 0. Non fare mai affidamento sull'overflow signed.