L'operatore sizeof in Linguaggio C
Il linguaggio C mette a disposizione una varietà di tipi fondamentali per memorizzare dati numerici e caratteri. Inoltre, come vedremo, in C è anche possibile creare array, stringhe e strutture dati composte.
Secondo lo standard del linguaggio C, solo il tipo char
ha una dimensione nota e fissa sempre a 1 byte. Tutti gli altri tipi fondamentali, e quelli da essi derivati, possono avere dimensioni diverse a seconda del compilatore, del processore e del sistema operativo su cui il programma viene eseguito.
Per questo motivo, e soprattutto per rendere il proprio codice scritto in C portabile su diverse piattaforme, è importante conoscere la dimensione di un tipo di dato o di una variabile. Per fare ciò, il linguaggio C mette a disposizione l'operatore sizeof
.
L'operatore sizeof
In linguaggio C sorgono spesso delle situazioni in cui è necessario conoscere la dimensione di un tipo di dato o di una variabile. In particolare, spesso serve conoscere quanta memoria in termini di byte è necessaria per memorizzare un tipo di dato.
Per risolvere questo problema, il linguaggio C mette a disposizione l'operatore sizeof
.
La sintassi dell'operatore è la seguente:
sizeof(nome_di_tipo)
Il risultato dell'espressione di sopra è un intero senza segno che rappresenta il numero di byte necessari a memorizzare un valore di quel tipo.
In generale, lo standard del linguaggio C assicura che la dimensione di un char
sia sempre pari ad un byte. Per cui, l'espressione che segue varrà sempre 1
:
/* sizeof(char) è sempre 1 */
sizeof(char)
Invece, il risultato dell'operatore sizeof
applicato ad altri tipi può restituire valori diversi a seconda del compilatore, del processore e del sistema operativo. Questa è, in sostanza, la motivazione della sua esistenza: permettere al programmatore di scrivere codice che sia indipendente dalla piattaforma su cui viene eseguito.
Grazie all'operatore sizeof
possiamo determinare la dimensione di un tipo di dato o di una variabile in modo dinamico, senza dover fare assunzioni sulla piattaforma su cui il programma viene eseguito.
Supponiamo di voler scrivere un programma che stampi a schermo la dimensione dei vari tipi di intero messi a disposizione dal linguaggio C. Possiamo farlo in questo modo:
#include <stdio.h>
int main() {
printf("Dimensione di un char: %lu byte\n", sizeof(char));
printf("Dimensione di un short: %lu byte\n", sizeof(short));
printf("Dimensione di un int: %lu byte\n", sizeof(int));
printf("Dimensione di un long: %lu byte\n", sizeof(long));
printf("Dimensione di un long long: %lu byte\n", sizeof(long long));
return 0;
}
Se proviamo a compilare ed eseguire questo programma su di una macchina a 64 bit, ad esempio su una macchina con un processore Intel x86_64 e un sistema operativo Linux, otterremo un output simile al seguente:
Dimensione di un char: 1 byte
Dimensione di un short: 2 byte
Dimensione di un int: 4 byte
Dimensione di un long: 8 byte
Dimensione di un long long: 8 byte
Come possiamo vedere, la dimensione di un char
è sempre 1 byte, mentre la dimensione degli altri tipi di intero varia a seconda della piattaforma su cui il programma viene eseguito.
Operatore sizeof
L'operatore sizeof
è un operatore unario che restituisce la dimensione in byte di un tipo di dato.
La sintassi dell'operatore sizeof
è la seguente:
sizeof(nome_di_tipo)
Il risultato dell'espressione sizeof(nome_di_tipo)
è un intero senza segno che rappresenta il numero di byte necessari a memorizzare un valore di quel tipo.
In generale, l'operatore sizeof
è diverso rispetto agli altri operatori nel senso che spesso il suo risultato è noto a tempo di compilazione quando applicato ad un tipo.
Infatti, molti compilatori ottimizzano il codice e calcolano il suo valore a tempo di compilazione, piuttosto che a tempo di esecuzione. Questo significa che l'operatore sizeof
è molto efficiente e non comporta alcun costo computazionale a tempo di esecuzione.
sizeof
applicato a variabili ed espressioni
L'operatore sizeof
è molto generale nella sua applicabilità.
Infatti, in linguaggio C è possibile applicarlo non solo a nomi di tipo, ma anche a variabili, costanti ed espressioni.
Se sizeof
viene applicato ad una variabile, il suo risultato sarà la dimensione in byte del tipo a cui la variabile appartiene.
Ad esempio, se abbiamo una variabile x
di tipo int
, possiamo scrivere:
int x;
printf("Dimensione di x: %lu byte\n", sizeof(x));
Il risultato di questa espressione sarà la dimensione in byte di un int
sulla piattaforma su cui il programma viene eseguito.
Analogamente, l'operatore può essere applicato ad una espressione. In questo caso, il risultato sarà la dimensione in byte del tipo di dato che l'espressione restituisce.
Per esempio, se abbiamo una espressione x + y
, possiamo scrivere:
int x, y;
/* ... */
printf("Dimensione di x + y: %lu byte\n", sizeof(x + y));
Nell'esempio di sopra, il risultato dell'espressione x + y
è un int
, per cui il risultato dell'operatore sizeof
sarà la dimensione in byte di un int
.
Quando sizeof
è applicato ad un'espressione, il compilatore calcola il tipo dell'espressione e restituisce la dimensione in byte di quel tipo.
Inoltre, quando applicato ad una variabile o ad una espressione, l'operatore sizeof
non necessita di parentesi. Possiamo, infatti, scrivere:
int x;
sizeof x;
Operatore sizeof
applicato a variabili ed espressioni
L'operatore sizeof
può essere applicato anche a variabili ed espressioni.
Se applicato ad una variabile la sintassi è la seguente:
sizeof(variabile)
In questo caso il risultato sarà la dimensione in byte del tipo a cui la variabile appartiene.
Se applicato ad un'espressione la sintassi è la seguente:
sizeof(espressione)
In questo caso il risultato sarà la dimensione in byte del tipo di dato che l'espressione restituisce.
In entrambe i casi le parentesi possono essere omesse:
sizeof variabile;
sizeof espressione;
Ma se le parentesi vengono omesse bisogna fare attenzione alla precedenza degli operatori.
Usare sempre le parentesi con sizeof
Sebbene l'operatore sizeof
applicato ad un'espressione o ad una variabile non necessiti di parentesi, è sempre buona prassi includerle.
Infatti, prendiamo la seguente espressione:
sizeof x + y;
Si potrebbe pensare erroneamente che il risultato di questa espressione sia sizeof
applicato all'addizione di x
e y
. In realtà, l'operatore di somma ha priorità inferiore rispetto a sizeof
, per cui il risultato di questa espressione sarà sizeof x
sommato a y
, ossia verrà interpretato come:
(sizeof x) + y;
Quindi, per ottenere il risultato desiderato, è sempre meglio includere le parentesi:
sizeof(x + y);
Il tipo size_t
Un'importante chiarificazione va fatta riguardo al tipo di dato che l'operatore sizeof
restituisce.
Abbiamo detto che esso restituisce un intero senza segno. A voler essere precisi, il tipo restituito dall'operatore sizeof
prende il nome di size_t
.
Il tipo size_t
è un tipo definito dalla libreria standard del linguaggio C (header <stddef.h>
) e rappresenta la dimensione di un oggetto in byte.
Si tratta, in effetti, di un intero senza segno. Tuttavia, la libreria standard C del compilatore garantisce che la dimensione di size_t
sia sufficiente per rappresentare la dimensione di qualsiasi oggetto in memoria.
La conseguenza di quanto detto sopra è che size_t
è un tipo di dato specifico della piattaforma. In altre parole, la dimensione di size_t
può variare a seconda del compilatore, del processore e del sistema operativo su cui il programma viene eseguito.
Tuttavia, ci possiamo aspettare che, ad esempio, su di un processore a 32 bit che è in grado di indirizzare 4 GB di memoria, la dimensione di size_t
sia di 4 byte. Invece, su di un processore a 64 bit, la dimensione di size_t
sarà di 8 byte.
In generale, quando lavoriamo con piccole quantità di memoria, non c'è nessun problema ad effettuare il cast di size_t
ad unsigned long
o unsigned long long
.
Il tipo size_t
Il tipo size_t
è un tipo di intero senza segno definito dalla libreria standard C, nell'header <stddef.h>
, in grado di rappresentare la dimensione di un oggetto in byte su una specifica piattaforma.
Il risultato dell'operatore sizeof
è di tipo size_t
.
La dimensione di size_t
può variare a seconda del compilatore, del processore e del sistema operativo su cui il programma viene eseguito.
Lo standard C garantisce, tuttavia, che la dimensione di size_t
sia sufficiente per rappresentare la dimensione di qualsiasi oggetto in memoria.
size_t
e printf
Abbiamo detto che possiamo sempre effettuare il cast di size_t
ad unsigned long
o unsigned long long
. Per cui, se vogliamo usare la funzione printf
per stampare un valore di tipo size_t
che rappresenta una quantità di memoria possiamo usare gli specificatori di formato %lu
o %llu
.
Per esempio, se vogliamo stampare la dimensione di un int
possiamo scrivere:
printf("Dimensione di un int: %lu byte\n", sizeof(int));
In C99, tuttavia, è stato introdotto uno specificatore di formato apposito per stampare valori di tipo size_t
. Questo specificatore di formato è %zu
.
Per cui, possiamo scrivere:
printf("Dimensione di un int: %zu byte\n", sizeof(int));
Specificatore di formato %zu
Lo specificatore di formato %zu
è stato introdotto in C99 per stampare valori di tipo size_t
:
#include <stddef.h>
size_t size;
printf("Dimensione: %zu byte\n", size);
In Sintesi
In questa lezione abbiamo introdotto un importante operatore del linguaggio C: sizeof
.
Abbiamo visto che:
- L'operatore
sizeof
restituisce la dimensione in byte di un tipo di dato. - L'operatore
sizeof
può essere applicato a nomi di tipo, variabili ed espressioni. - Il risultato dell'operatore
sizeof
è di tiposize_t
, un tipo specifico della piattaforma. size_t
è un tipo di intero senza segno definito dalla libreria standard C.size_t
è in grado di rappresentare la dimensione di qualsiasi oggetto in memoria.- Possiamo usare
size_t
con la funzioneprintf
usando lo specificatore di formato%zu
.
L'importanza di sizeof
sarà evidente quando affronteremo argomenti più avanzati, come l'allocazione dinamica della memoria e la manipolazione di array e stringhe.