Menu

Tipi di dati in C: int, float, char, double e altri

Ogni variabile C ha un tipo che ne fissa la dimensione e ciò che può contenere. Ecco tutti i tipi base, le loro dimensioni reali, signed e unsigned, sizeof, limits.h e cosa succede in caso di overflow.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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

  • char contiene 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.
  • float e double contengono numeri reali. double ha circa il doppio della precisione.
  • void è in un certo senso il quarto tipo, ma significa "nessun valore": non puoi dichiarare una variabile void, 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:

TipoDimensione tipicaIntervallo tipico
char1 byteda -128 a 127 (o da 0 a 255)
short2 byteda -32.768 a 32.767
int4 byteda -2.147.483.648 a 2.147.483.647
long8 byte (4 su Windows)circa ±9,2 trilioni
long long8 bytecirca ±9,2 trilioni
float4 byte~7 cifre significative
double8 byte~15 cifre significative
long double16 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, oppure int64_t di stdint.h.
  • Dimensioni, lunghezze, indici di array che arrivano da sizeof o strlen: size_t. È unsigned e garantito abbastanza grande per qualsiasi oggetto.
  • Decimali: double. Usa float solo per dimezzare la memoria in array grandi o su hardware embedded senza unità a doppia precisione.
  • Singoli caratteri e byte grezzi: char per il testo, unsigned char per i dati binari.
  • Vero/falso: bool di stdbool.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_t e 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.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA