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.

Definizione

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;
Definizione

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.

Nota

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.

Definizione

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));
Definizione

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 tipo size_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 funzione printf 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.