Una costante è un valore con un nome e la promessa che non cambierà. Il C ti offre tre meccanismi per farlo, e sotto il cofano sono cose davvero diverse: una è una variabile, una è una sostituzione di testo e una è un tipo intero.
Perché dare un nome a un valore
Confronta queste due righe:
double area = 3.14159 * r * r;
double area = PI * r * r;
La seconda è migliore per tre motivi che emergono tutti più avanti. Dice cosa significa il numero. Compare in un solo posto, quindi cambiare la precisione è una sola modifica invece di una ricerca. E un refuso in PI è un errore di compilazione, mentre un refuso in 3.14195 è una risposta sbagliata.
I numeri senza nome sparsi nel codice si chiamano numeri magici, ed eliminarli è uno dei miglioramenti più economici che puoi fare a un programma C.
const
La parola chiave const rende una variabile di sola lettura:
Togli il commento all'assegnazione e il compilatore si rifiuta: assignment of read-only variable 'PI'. Quell'errore in fase di build è tutto il senso della cosa.
Una variabile const è una vera variabile. Ha un tipo, quindi il compilatore controlla come la usi. Rispetta lo scope, quindi una const dentro una funzione non esce fuori. Ha un indirizzo, quindi puoi prenderne un puntatore. E un debugger può mostrarti il suo nome e il suo valore.
const deve essere inizializzata nel punto della dichiarazione: non c'è una seconda occasione:
const int limit; /* errore (o spazzatura per sempre): niente può assegnarle un valore dopo */
const int limit = 50; /* corretto */
#define
L'alternativa del preprocessore è una macro:
Questa non è una variabile. Prima che il compilatore parta, il preprocessore sostituisce ogni occorrenza di PI nel sorgente con i caratteri 3.14159265358979. Il compilatore non vede mai il nome, ed è proprio per questo che un #define non ha tipo e non può comparire in un debugger.
Nota che non ci sono né punto e virgola né =. Una macro è testo grezzo, quindi questo errore comune:
#define MAX_USERS 100; /* nota il punto e virgola */
int limit = MAX_USERS + 1; /* si espande in: int limit = 100; + 1; -> errore */
produce un errore su una riga che non assomiglia per niente alla definizione. La convenzione dei nomi in maiuscolo per le macro esiste proprio perché chi legge sappia che sta avvenendo una sostituzione.
Per convenzione le macro si scrivono in UPPER_CASE; di solito anche le variabili const la seguono quando rappresentano la stessa idea.
Costanti enum
La terza forma ti dà un insieme di interi con un nome:
I valori partono da 0 e aumentano di uno, a meno che tu non li assegni, quindi MON vale 0 e FRI vale 4. Un enum { ... } anonimo è un idioma comune per semplici costanti intere.
Le costanti enum hanno un vantaggio reale su entrambe le alternative: sono costanti intere di compilazione che il compilatore capisce, e sono visibili al debugger. La storia completa è nella pagina sugli enum in C.
Quale usare
| Ti serve | Usa |
|---|---|
| Una costante tipizzata, con scope e visibile nel debugger | const |
Una dimensione di array, un'etichetta case, la larghezza di un bitfield | enum o #define |
| Una costante stringa | #define o const char * |
| Una costante in virgola mobile | const double |
| Un insieme di valori interi collegati | enum |
Qualsiasi cosa debba vedere il preprocessore (#if, include guard) | #define |
La scelta predefinita dovrebbe essere const. Usa #define quando il valore deve esistere prima della compilazione ed enum quando ti serve un intero di compilazione o un gruppo con un nome.
L'unico punto in cui questo fa davvero male sono le dimensioni degli array:
In C una const int è una variabile di sola lettura, non un'espressione costante di compilazione. Dentro una funzione C99 accetta int local[const_size] trasformandolo in un array a lunghezza variabile; a livello di file, in una struct o come etichetta case viene rifiutata del tutto. enum e #define funzionano ovunque.
(Questa è una delle vere differenze rispetto al C++, dove const int è un'espressione costante. Il codice che passa da un linguaggio all'altro ci inciampa.)
const con i puntatori
const e i puntatori si combinano in tre modi, e i significati non sono intercambiabili. Leggi la dichiarazione dal nome verso l'esterno:
const char *p; /* p punta a char che non devono essere modificati */
char * const q; /* q non può puntare altrove; i char possono cambiare */
const char * const r; /* non possono cambiare né il puntatore né i char */
La forma const char * è quella che scriverai più spesso, perché è il tipo corretto per un parametro di funzione che legge una stringa senza modificarla:
Aggiungere const a un parametro di sola lettura non costa nulla e documenta il contratto in un modo che il compilatore fa rispettare. Vale la pena farlo ovunque.
Le stringhe letterali sono di fatto const
Una trappola sottile che const esiste per intercettare:
char *s = "hello"; /* C valido, ma s punta a memoria di sola lettura */
s[0] = 'H'; /* comportamento indefinito: di solito un crash */
Le stringhe letterali vivono in una sezione di sola lettura dell'eseguibile. Scrivere tramite quel puntatore di solito provoca un segfault. Dichiarare il puntatore come const char *s = "hello"; fa sì che il compilatore rifiuti l'assegnazione invece di lasciarla andare in crash a runtime.
Se ti serve una stringa modificabile, copiala in un array:
Domande frequenti
Come si dichiara una costante in C?
Aggiungi const a una dichiarazione e assegnale un valore: const double PI = 3.14159;. Da quel momento il compilatore rifiuta qualsiasi tentativo di assegnarle un valore. L'alternativa più vecchia è una macro del preprocessore, #define PI 3.14159, che invece fa una sostituzione di testo.
Qual è la differenza tra const e #define in C?
const crea una vera variabile tipizzata che il compilatore conosce, quindi viene controllata nei tipi, compare nel debugger e rispetta lo scope. #define è pura sostituzione di testo fatta prima della compilazione: non ha tipo, non ha scope e non si può ispezionare durante il debug. Preferisci const, a meno che il valore ti serva in un contesto che richiede una costante di compilazione.
Una variabile const si può usare come dimensione di un array in C?
Non per un array di dimensione fissa a livello di file. const int N = 10; int arr[N]; è un array a lunghezza variabile in C99 (consentito dentro una funzione, non a livello di file e non in una struct). Per una vera dimensione di compilazione usa #define N 10 oppure enum { N = 10 };.
Cosa significa const char *p?
È un puntatore a un char costante: puoi cambiare dove punta p, ma non il carattere a cui punta. char * const p è l'opposto: un puntatore costante a un char modificabile. const char * const p blocca entrambi. Leggi la dichiarazione partendo dal nome verso l'esterno.