Inizializzatori in Linguaggio C

Gli inizializzatori in linguaggio C consentono di assegnare valori alle variabili fin dalla loro dichiarazione, migliorando la chiarezza del codice e riducendo potenziali errori.

In questa lezione esploreremo i diversi tipi di inizializzatori, le regole che governano la loro validità e le differenze tra variabili con durata di archiviazione automatica e statica.

Inizializzatori

Per comodità, il C ci permette di specificare valori iniziali per le variabili mentre le dichiariamo.

Abbiamo, infatti, visto nelle lezioni precedenti che per inizializzare una variabile, scriviamo il simbolo = dopo il suo dichiaratore, seguito da un inizializzatore. (Non confondiamo il simbolo = in una dichiarazione con l'operatore di assegnazione: l'inizializzazione non è la stessa cosa dell'assegnamento.)

Abbiamo già visto vari tipi di inizializzatori nelle lezioni precedenti. L'inizializzatore di una variabile semplice è un'espressione dello stesso tipo della variabile:

int i = 5 / 2; /* i è inizialmente 2 */

Se i tipi non corrispondono, il C converte l'inizializzatore usando le stesse regole dell'assegnamento:

int j = 5.5;  /* convertito in 5 */

L'inizializzatore di una variabile puntatore deve essere un'espressione di puntatore dello stesso tipo della variabile o di tipo void *:

int *p = &i;

L'inizializzatore per un array, una struttura o un'unione di solito è una serie di valori racchiusi tra parentesi graffe:

int a[5] = {1, 2, 3, 4, 5};

Inoltre, abbiamo visto che in C99 possiamo adoperare i cosiddetti inizializzatori designati:

int a[5] = {[2] = 3, [4] = 5};

/* è equivalente a */

int a[5] = {0, 0, 3, 0, 5};

Per completare la panoramica sulle dichiarazioni, esaminiamo ora alcune regole aggiuntive che governano gli inizializzatori:

  • Un inizializzatore per una variabile con durata di archiviazione statica deve essere costante:

    #define FIRST 1
    #define LAST 100
    
    static int i = LAST - FIRST + 1;
    

    Poiché LAST e FIRST sono macro, il compilatore può calcolarne il valore iniziale (100 - 1 + 1 = 100). Se LAST e FIRST fossero state variabili, l'inizializzatore sarebbe stato illegale.

  • Se una variabile ha durata di archiviazione automatica, il suo inizializzatore non deve essere necessariamente costante:

    int f(int n)
    {
        int last = n - 1;
        ...
    }
    
  • Un inizializzatore racchiuso tra parentesi graffe per un array, una struttura o un'unione deve contenere solo espressioni costanti, mai variabili o chiamate di funzione:

    #define N 2
    int powers[5] = {1, N, N*N, N*N*N, N*N*N*N};
    

    Poiché N è una costante, l'inizializzatore per powers è valido; se N fosse una variabile, il programma non compilerebbe. In C99, questa restrizione vale solo se la variabile ha durata di archiviazione statica.

  • L'inizializzatore per una struttura o union automatica può essere un'altra struttura o unione:

    void g(struct part part1)
    {
        struct part part2 = part1;
        ...
    }
    

    L'inizializzatore non deve per forza essere il nome di una variabile o di un parametro, ma deve essere un'espressione del tipo corretto. Ad esempio, l'inizializzatore di part2 potrebbe essere *p, dove p è di tipo struct part *, oppure f(part1), dove f è una funzione che restituisce una struttura di tipo part.

Variabili non Inizializzate

Nelle lezioni precedenti abbiamo visto che le variabili non inizializzate hanno valori indefiniti. Ciò perché potrebbero avere il valore di un'area di memoria precedentemente occupata, o potrebbero essere allocate in una parte della memoria che non è stata inizializzata. Tuttavia, è un errore comune pensare che le variabili non inizializzate siano sempre impostate a zero.

Non è sempre vero però: il valore iniziale di una variabile dipende dalla sua durata di archiviazione.

  • Le variabili con durata di archiviazione automatica non hanno un valore iniziale predefinito. Il valore iniziale di una variabile automatica non può essere previsto e può variare ogni volta che la variabile entra in esistenza.

  • Le variabili con durata di archiviazione statica assumono il valore zero di default. A differenza della memoria allocata con calloc, che semplicemente imposta i bit a zero, una variabile statica viene inizializzata correttamente in base al suo tipo: le variabili di tipo intero sono inizializzate a 0, quelle a virgola mobile a 0.0, mentre i puntatori contengono un puntatore nullo.

Dal punto di vista stilistico, è preferibile fornire inizializzatori per le variabili statiche piuttosto che fare affidamento sul fatto che siano garantite a zero. Se un programma accede a una variabile che non è stata esplicitamente inizializzata, chi legge il programma non può facilmente determinare se la variabile è effettivamente considerata zero o se viene inizializzata in un altro punto del programma.

In Sintesi

In questa lezione abbiamo visto che:

  • Un inizializzatore viene specificato scrivendo = dopo il dichiaratore, seguito da un'espressione o da un elenco di valori (nel caso di array, strutture e unioni).
  • Le variabili con durata di archiviazione statica devono avere un inizializzatore costante, mentre quelle automatiche possono essere inizializzate con valori dinamici.
  • In C99 è possibile usare gli inizializzatori designati e altre forme avanzate per inizializzare array e strutture in modo più flessibile.
  • Variabili non inizializzate hanno comportamenti diversi a seconda della loro durata di archiviazione: quelle statiche sono azzerate, mentre quelle automatiche hanno un valore indeterminato.
  • È buona pratica fornire sempre inizializzatori, specialmente per le variabili statiche, per evitare ambiguità e rendere chiaro il comportamento del programma.