Qualificatori di Tipo in Linguaggio C

I Qualificatori di Tipo in linguaggio C sono utilizzati per specificare come un valore possa modificarsi nel tempo.

In Linguaggio C ne esistono quattro:

  • const
  • volatile
  • restrict
  • _Atomic

Il qualificatore restrict lo abbiamo già studiato quando abbiamo affrontato i puntatori limitati o puntatori restricted e, infatti, si applica solo ai puntatori.

Il qualificatore volatile si usa per la programmazione di basso livello e lo studieremo nelle prossime lezioni. Mentre, il qualificatore _Atomic si usa per la programmazione concorrente e anche il suo studio è rimandato a lezioni successive.

In questa lezione ci concentreremo sul qualificatore const e sul suo utilizzo.

Qualificatore const

Il qualificatore const posto davanti la dichiarazione di una variabile consente di definire entità che rassomigliano ad una variabile ma che in realtà possono essere accedute in sola lettura. In altre parole, un programma può accedere al valore di un oggetto const ma non ne può modificare il contenuto.

Prendiamo l'esempio che segue:

const int numero = 10;

In questo caso abbiamo creato un oggetto const di tipo int chiamato numero e gli abbiamo assegnato il valore 10. Questo significa che il valore di numero non può essere modificato nel corso dell'esecuzione del programma.

Analogamente, possiamo creare un array di const:

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

In questo caso abbiamo creato un array di const di tipo int chiamato numeri e gli abbiamo assegnato i valori {1, 2, 3, 4, 5}. Anche in questo caso, il contenuto dell'array numeri non può essere modificato.

Dichiarare un oggetto come const fornisce una serie di vantaggi:

  • Leggibilità del codice:

    il qualificatore const indica che il valore di un oggetto non cambierà nel corso del programma, rendendo il codice più leggibile e più facile da manutenere. Inoltre, uno sviluppatore che legge il codice può capire immediatamente che un oggetto è destinato a rimanere invariato.

  • Prevenzione di errori:

    il qualificatore const previene la modifica accidentale di un oggetto. Se un programma tenta di modificare un oggetto const, il compilatore genererà un errore.

  • Ottimizzazione del codice:

    il qualificatore const consente al compilatore di effettuare ottimizzazioni del codice. Ad esempio, il compilatore può memorizzare un oggetto const in una posizione di memoria diversa rispetto ad un oggetto non const, poiché sa che il valore di un oggetto const non cambierà.

  • Programmazione Embedded:

    il qualificatore const è particolarmente utile nella programmazione embedded, dove la memoria è limitata e la prevenzione di errori è fondamentale. Inoltre, il qualificatore const può essere utilizzato per memorizzare costanti in memoria di sola lettura (ROM). Ad esempio, quando si sviluppa per piattaforme come Arduino, è comune utilizzare il qualificatore const per definire costanti che vengono memorizzate nella EEPROM o nella memoria Flash piuttosto che nella RAM.

Ricapitolando:

Definizione

Qualificatore const in Linguaggio C

Il qualificatore const in linguaggio C consente di definire entità che possono essere accedute in sola lettura. Le variabili const non possono essere modificate nel corso dell'esecuzione del programma.

La sintassi per adoperare il qualificatore const è la seguente:

const tipo nome = valore;

dove tipo è il tipo di dato dell'oggetto, nome è il nome dell'oggetto e valore è il valore iniziale dell'oggetto.

Nota

Un oggetto const deve essere sempre inizializzato

Un oggetto const deve essere sempre inizializzato al momento della dichiarazione:

const int numero = 10; // Corretto

In realtà, è possibile dichiarare un oggetto const senza inizializzarlo. Tuttavia, questa pratica non solo ha poco senso, ma la conseguenza è che l'oggetto non può essere modificato. Per cui, il valore che conterrà sarà completamente casuale.

Ad esempio, se scriviamo un programma simile:

#include <stdio.h>

int main() {
    /* Non Inizializzato */
    const int numero;
    printf("Numero: %d\n", numero);
    return 0;
}

Il programma potrebbe stampare un valore casuale, come 0, 1, -1, ecc. Questo perché l'oggetto numero non è stato inizializzato e il suo valore è indefinito.

Differenza tra const e #define

Abbiamo visto in precedenza che nel codice scritto in C possiamo definire delle costanti attraverso la direttiva di precompilazione #define.

Sorge spontanea a questo punto la domanda: perché fornire due meccanismi diversi per definire delle costanti?

Il fatto è che esistono delle sostanziali differenze tra l'uso del qualificatore const e la direttiva #define:

  • La direttiva #define può essere adoperata per creare costanti di tipo numerico, carattere o stringa. Mentre, il qualificatore const può essere utilizzato per creare costanti di qualunque tipo: array, strutture dati, puntatori e union.
  • Le costanti definite con #define sono sostituite dal preprocessore con il valore definito. Mentre, le costanti definite con const sono memorizzate in memoria e possono essere trattate come variabili normali.

    La conseguenza è che gli oggetti const sono soggetti alle stesse regole di visibilità a cui sono soggette le variabili. Ad esempio, un oggetto const dichiarato all'interno di una funzione è visibile solo all'interno di quella funzione. Mentre, una costante definita con #define è visibile in tutto il file sorgente.

  • Il valore di un oggetto const può essere visto dal debugger, mentre il valore di una costante definita con #define no.

  • Le costanti definite con #define non sono tipizzate. Mentre, le costanti definite con const sono tipizzate e il compilatore può effettuare controlli di tipo.
  • Si può applicare l'operatore indirizzo & per ottenere l'indirizzo di un oggetto const in quanto esso occupa effettivamente spazio in memoria. Mentre, non si può applicare l'operatore indirizzo a una costante definita con #define in quanto essa non occupa spazio in memoria.

Oltre queste differenze, ne esiste una fondamentale che riguarda l'uso degli oggetti const in espressioni costanti.

Nello standard C89, non si può adoperare un oggetto const in un'espressione costante. Per cui, ad esempio, il codice che segue non è valido:

#define N 10

const int M = 10;

/* Valido */
int a[N];

/* NON Valido in C89 */
int b[M];

In questo esempio, N è una costante definita con #define e M è un oggetto const. In C89, N può essere utilizzato per definire la dimensione di un array, mentre M no.

Nello standard C99 questo limite è stato rilassato. Quindi si può adoperare un oggetto const in un'espressione costante purché l'oggetto const abbia come classe di storage auto, ossia abbia durata di vita automatica.

Quindi, in C99, il seguente codice diventa valido:

static const int N = 10;

void funzione() {
    const int M = 10;

    /* Valido in C99 */
    int a[M];

    /* NON Valido in C99 */
    int b[N];
}

In questo esempio, N è un oggetto const con classe di storage static, mentre M è un oggetto const con classe di storage auto. In C99, M può essere utilizzato per definire la dimensione di un array, mentre N no.

In generale, non esiste una regola definitiva da seguire per scegliere se usare const oppure #define per definire delle costanti. La scelta dipende dal contesto e dalle esigenze specifiche del programma.

Una regola di massima è quella di adoperare costanti definite con la direttiva #define quando si tratta di costanti numeriche o di stringhe semplici. Questo perché, così facendo, possiamo usare le costanti nella definizione della dimensione di array, nelle istruzioni switch e in tutti quei casi in cui sono richieste espressioni costanti.

In Sintesi

In questa lezione abbiamo visto come utilizzare il qualificatore const in linguaggio C per definire costanti. Le costanti definite con const sono oggetti che possono essere acceduti in sola lettura e non possono essere modificati nel corso dell'esecuzione del programma.

Abbiamo anche confrontato l'uso del qualificatore const con la direttiva #define per definire costanti e abbiamo visto le differenze tra i due approcci.

Il qualificatore const è uno strumento potente che può migliorare la leggibilità del codice, prevenire errori e ottimizzare il codice. È particolarmente utile nella programmazione embedded e in situazioni in cui è necessario definire costanti di qualunque tipo.

Inoltre, abbiamo visto che in C99 è possibile utilizzare un oggetto const in un'espressione costante purché l'oggetto const abbia come classe di storage auto.

In conclusione, il qualificatore const è uno strumento fondamentale per la scrittura di codice C robusto e manutenibile.