Tipi Interi in Linguaggio C
In questa guida sul linguaggio C abbiamo, fino ad ora, utilizzato soltanto due tipi di dato di base (built-in): int
e float
.
Adesso è il momento di approfondire il catalogo dei tipi di dato di base del linguaggio C. In questa lezione, in particolare, inizieremo a studiare tipi numerici interi.
Tipi Interi
Il linguaggio C supporta due differenti gruppi di tipi numerici: i tipi interi e i tipi in virgola mobile chiamati anche tipi floating point.
I valori di tipo intero rappresentano, appunto, numeri interi privi di parte frazionaria. I tipi in virgola mobile, invece, consentono di rappresentare numeri dotati anche di parte frazionaria.
A loro volta, i tipi interi si suddividono in due categorie: i tipi interi con segno e i tipi interi senza segno chiamati, ripettivamente, interi signed e interi unsigned.
Tipi Interi
In Linguaggio C i tipi interi vengono utilizzati per rappresentare numeri interi privi di parte frazionaria. I tipi interi si suddividono in due categorie: i tipi interi con segno e i tipi interi senza segno.
Interi con segno e senza segno
Per comprendere appieno la differenza tra i tipi interi con segno e senza segno, è necessario capire come vengono rappresentati i numeri in memoria.
Senza entrare troppo nel dettaglio, basta sapere che i numeri interi con segno vengono rappresentati riservando il bit più significativo, ossia il bit più a sinistra (lefmost bit) per il segno. Questo significa che un numero intero con segno può assumere valori sia positivi che negativi.
Ad esempio, se per rappresentare un intero usiamo 16 bit, il bit più a sinistra sarà riservato per il segno, mentre i restanti 15 bit saranno utilizzati per rappresentare il valore numerico.
Se prendiamo il seguente numero binario a 16 bit:
Questo numero rappresenta il valore decimale 32767 ossia
Analogamente, se avessimo usato 8 bit per rappresentare un numero intero con segno, il massimo valore rappresentabile sarebbe 127, ossia
Non entriamo, qui, nel merito di come siano rappresentati i numeri in sistema binario, ma è importante comprendere che i numeri interi con segno possono assumere valori sia positivi che negativi.
Quando, invece, abbiamo a che fare con numeri interi senza segno, il bit più a sinistra non è riservato per il segno, ma viene utilizzato per rappresentare il valore numerico insieme ai bit restanti. Questo significa che i numeri interi senza segno possono assumere solo valori positivi, incluso lo zero.
Ad esempio, se prendiamo il numero a 16 bit composto da soli 1:
Questo numero rappresenta il valore decimale 65535, ossia
Viceversa, il numero minimo senza segno che possiamo rappresentare con 16 bit è, giustamente, 0:
Interi con segno e senza segno
In Linguaggio C, i tipi interi si suddividono in due categorie: i tipi interi con segno e i tipi interi senza segno.
- I tipi interi con segno (signed) possono rappresentare valori positivi e negativi. Il bit più a sinistra è riservato per il segno.
- I tipi interi senza segno (unsigned) possono rappresentare solo valori positivi, zero incluso. Il bit più a sinistra è utilizzato per rappresentare il valore numerico.
In generale, la formula per determinare l'intervallo di rappresentazione dei numeri interi può essere espressa come segue:
- Se abbiamo
bit a disposizione, l'intervallo di rappresentazione dei numeri interi con segno sarà:
- Mentre l'intervallo di rappresentazione dei numeri interi senza segno sarà:
Di default, il linguaggio C considera tutti gli interi come interi con segno. Per dichiarare un intero senza segno, è necessario utilizzare il qualificatore unsigned
.
La sintassi per specificare un intero senza segno consiste nel preporre la parola chiave unsigned
al tipo di dato. Quindi, ad esempio, possiamo scrivere:
unsigned int x;
Analogamente, per specificare che si tratta di un intero con segno si adopera il qualificatore signed
. Tuttavia, poiché tutti gli interi sono considerati di default come interi con segno, l'uso di signed
è superfluo.
signed
e unsigned
In Linguaggio C, per specificare che un intero è senza segno si utilizza il qualificatore unsigned
. Per specificare che un intero è con segno si può utilizzare il qualificatore signed
, ma esso è superfluo poiché tutti gli interi sono considerati di default come interi con segno.
La sintassi è la seguente:
/* Intero senza segno */
unsigned int x;
/* Intero con segno */
signed int x;
Sia signed
che unsigned
sono parole chiave riservate del linguaggio C e non possono essere utilizzate come identificatori.
Ad esempio, le due scritture seguenti sono equivalenti:
/* Equivalenti */
signed int y;
int y;
I tipi interi senza segno sono spesso utilizzati per lavorare a basso livello, ossia per interagire con l'hardware della macchina o per scrivere programmi di sistema. Altri tipi di applicazione possono essere, ad esempio, la rappresentazione di valori che non possono essere negativi, come l'età di una persona o il numero di elementi in un array.
Man mano vedremo, nel corso di questa guida, quando conviene adoperare un tipo di dato piuttosto che un altro.
Dimensione dei tipi interi
Il linguaggio C mette a disposizione differenti tipi interi con dimensioni diverse. Tipicamente, il tipo int
è un intero a 32 bit, ma non sempre ciò è vero.
La verità è che la dimensione di int
dipende da due fattori:
- L'architettura della macchina su cui il programma viene eseguito;
- Il compilatore utilizzato per compilare il programma.
Tipicamente, su processori a 32 bit, il tipo intero int
è a 32 bit, mentre su processori a 64 bit, int
è a 64 bit. Tuttavia, non è garantito che ciò sia vero in tutti i casi.
Il tipo int
non è l'unico tipo intero. Il linguaggio C mette a disposizione anche il tipo long
che permette di memorizzare numeri molto grandi, ed il tipo short
che può essere adoperato quando dobbiamo salvare spazio, ossia quando dobbiamo memorizzare un numero in un numero di bit inferiore.
Per costruire un intero della dimensione desiderata si possono combinare questi qualificatori con il tipo int
ottenendo, di fatto, sei possibili combinazioni:
/* Short Integer con segno */
short int a;
/* Short Integer senza segno */
unsigned short int b;
/* Integer con segno */
int c;
/* Integer senza segno */
unsigned int d;
/* Long Integer con segno */
long int e;
/* Long Integer senza segno */
unsigned long int f;
La sintassi del C permette, inoltre, di omettere la parola chiave int
quando si dichiara un intero. Quindi possiamo scrivere anche:
/* Equivalenti */
short a;
unsigned short b;
int c;
unsigned d;
long e;
unsigned long f;
Questa pratica di omettere int
è molto comune nei programmi scritti in C. Essa è talmente comune che nei linguaggi con sintassi derivata dal C, come Java e C#, per dichiarare un intero short
o long
bisogna omettere int
.
Per questo motivo, in questa guidai adotteremo la pratica di omettere int
quando dichiariamo un intero short
, long
o unsigned
.
Come abbiamo detto, la dimensione di un tipo intero dipende dall'architettura della macchina e dal compilatore. Tuttavia, esiste una serie di regole che tutti i compilatori C devono rispettare:
-
Sebbene i tipi interi possono avere dimensioni diverse, il compilatore deve garantire che essi abbiano una dimensione minima e una dimensione massima:
- Un intero
short
deve avere almeno 16 bit; - Un intero
int
deve avere almeno 16 bit; - Un intero
long
deve avere almeno 32 bit.
- Un intero
-
La dimensione di un intero
short
deve essere minore o uguale a quella di un interoint
, che a sua volta deve essere minore o uguale a quella di un interolong
.La conseguenza è che un
int
potrebbe avere la stessa dimensione di unoshort
, e unlong
potrebbe avere la stessa dimensione di unint
. Ma non deve mai accadere che unshort
abbia una dimensione maggiore di unint
, o che unint
abbia una dimensione maggiore di unlong
:
Dimensione degli interi in linguaggio C
Lo standard del linguaggio C non impone una dimensione specifica per i tipi interi. La dimensione di un tipo intero dipende dall'architettura della macchina e dal compilatore utilizzato.
Tuttavia lo standard impone che:
- Un intero
short
deve avere almeno 16 bit; - Un intero
int
deve avere almeno 16 bit; - Un intero
long
deve avere almeno 32 bit.
Inoltre, la dimensione di un intero short
deve essere minore o uguale a quella di un intero int
, che a sua volta deve essere minore o uguale a quella di un intero long
.
In generale, ci si può aspettare delle dimensioni tipiche a seconda dell'architettura della macchina.
Nella tabella che segue, mostriamo le dimensioni tipiche degli interi per un processore a 16 bit:
Tipo | Dimensione | Valore Minimo | Valore Massimo |
---|---|---|---|
short |
16 bit | ||
unsigned short |
16 bit | ||
int |
16 bit | ||
unsigned int |
16 bit | ||
long |
32 bit | ||
unsigned long |
32 bit |
La prossima tabella mostra, invece, le dimensioni tipiche degli interi per un processore a 32 bit:
Tipo | Dimensione | Valore Minimo | Valore Massimo |
---|---|---|---|
short |
16 bit | ||
unsigned short |
16 bit | ||
int |
32 bit | ||
unsigned int |
32 bit | ||
long |
32 bit | ||
unsigned long |
32 bit |
Infine, la tabella seguente mostra le dimensioni tipiche degli interi per un processore a 64 bit:
Tipo | Dimensione | Valore Minimo | Valore Massimo |
---|---|---|---|
short |
16 bit | ||
unsigned short |
16 bit | ||
int |
32 bit | ||
unsigned int |
32 bit | ||
long |
64 bit | ||
unsigned long |
64 bit |
Anche se abbiamo riportato delle tabelle che mostrano la dimensione tipica degli interi a seconda dell'architettura del processore, è bene ricordare che le dimensioni riportate non sono mandatorie. Ciò vuol dire che non necessariamente un intero int
su un processore a 32 bit sarà a 32 bit. Tuttavia, è molto probabile che ciò accada.
Esistono vari modi per determinare la dimensione di un tipo intero su una specifica architettura. Li vedremo nelle prossime lezioni.
Interi nello standard C99
Lo standard del C99 fornisce due nuovi tipi di intero: long long int
e unsigned long long int
.
Questi tipi furono aggiunti per andare incontro alle esigenze, sempre crescenti, di poter rappresentare e manipolare valori numerici sempre più grandi sui nuovi processori a 64 bit.
In base a quanto asserisce lo standard C99, una variabile di tipo long long int
o di tipo unsigned long long int
deve avere almeno 64 bit. Per cui, in base a ciò, siamo sicuri che tali tipi avranno le seguenti caratteristiche, indipendentemente dal compilatore o dall'architettura della macchina:
Tipo | Dimensione | Valore Minimo Tipico | Valore Massimo Tipico |
---|---|---|---|
long long int |
64 bit | ||
unsigned long long int |
64 bit |
Ovviamente, a seconda dell'implementazione del compilatore, i tipi long long int
e unsigned long long int
potrebbero avere dimensioni maggiori di 64 bit.
In generale, nello standard C99 i tipi short
, int
, long
e long long
prendono il nome di tipi interi standard con segno (standard signed integer types).
Viceversa, i tipi unsigned short
, unsigned int
, unsigned long
e unsigned long long
prendono il nome di tipi interi standard senza segno (standard unsigned integer types).
Questi nomi sono stati scelti per differenziarli dai tipi interi estesi (extended integer types), che sono tipi interi che possono avere dimensioni maggiori di quelli standard e che i vari compilatori possono implementare a loro discrezione.
Tipi interi long long
in C99
Nello standard C99 sono stati introdotti due nuovi tipi interi: long long int
e unsigned long long int
.
In base allo standard, le variabili long long
e le variabili unsigned long long
devono utilizzare almeno 64 bit.
Tipi interi a dimensione nota
Per ovviare al problema della dimensione variabile dei tipi interi, lo standard C99 ha introdotto una serie di tipi interi a dimensione nota.
Tali tipi sono definiti nell'header stdint.h
e sono i seguenti:
int8_t
: intero a 8 bit con segno;uint8_t
: intero a 8 bit senza segno;int16_t
: intero a 16 bit con segno;uint16_t
: intero a 16 bit senza segno;int32_t
: intero a 32 bit con segno;uint32_t
: intero a 32 bit senza segno;int64_t
: intero a 64 bit con segno;uint64_t
: intero a 64 bit senza segno.
Questi tipi sono garantiti avere una dimensione fissa e sono stati introdotti per garantire la portabilità dei programmi C su differenti architetture.
Per cui, se dichiariamo, ad esempio, una variabile di tipo int32_t
, possiamo essere certi che essa avrà sempre 32 bit, indipendentemente dall'architettura della macchina:
#include <stdint.h>
int32_t x;
In Sintesi
In questa lezione abbiamo approfondito il catalogo dei tipi numerici interi che il linguaggio C mette a disposizione del programmatore.
In particolare, abbiamo studiato che:
- I tipi interi rappresentano numeri interi privi di parte frazionaria;
- I tipi interi si suddividono in due categorie: i tipi interi con segno e i tipi interi senza segno;
- I tipi interi con segno possono rappresentare valori positivi e negativi, mentre i tipi interi senza segno possono rappresentare solo valori positivi;
- La dimensione di un tipo intero dipende dall'architettura della macchina e dal compilatore utilizzato;
- Lo standard C99 ha introdotto i tipi
long long int
eunsigned long long int
per rappresentare numeri molto grandi; - Lo standard C99 ha introdotto i tipi interi a dimensione nota per garantire la portabilità dei programmi C su differenti architetture.
Nella prossima lezione studieremo le costanti intere, chiamati anche valori letterali interi, e vedremo come si possono scrivere numeri nel codice sorgente.