Const - STB1019/SkullOfSummer GitHub Wiki
const
è una reserved word particolare in C, che spesso viene scambiata per generare una costante.
In realtà, const
nasconde molte più insidie che quelle che fa trasparire.
const
è un type qualifier (assieme a volatile
ed a restrict
) che afferma che l'oggetto (leggasi valore) associato non può essere modificato tramite una modifica esplicita del suo lvalue (vedi standard 6.7.3).
Ok, è complicato. Andiamo per passi:
Un lvalue, informalmente, è il nome della variabile coinvolta, eventualmente accompagnata dagli operatori [
, ]
, &
, ++
, --
, o *
. Per esempio:
-
pippo
è un lvalue; -
pippo++
è un lvalue; -
pippo+3
non è un lvalue;
In generale un espression è un lvalue
se può stare a sinistra dell =
durante un assegnamento. Lo standard descrive un lvalue in sezione 6.3.2.1. Possiamo quindi dare una definizione più chiara:
Quindi una variabile
X
qualificata conconst
non può essere modificata tramite un'operazione che coinvolgalvalue
della variabileX
stessa.
Per esempio, se const int a=5
, allora:
-
a+4
è lecito; -
a=4;
non lo è (perché stiamo modificando la variabile tramite lvalue);
L'uso classico è const tipo_primitivo nome_variabile
. Per esempio:
const int a;
Per le variabili di tipo puntatore, è possibile fare 2 distinguo:
-
const int* a
eint const* a
indicano puntatori variabili (ossia che possono essere alterati) che puntano ad una costante intera. Non è quindi possibile fare giochi come*a += 1
; -
int* const a
indica un puntatore costante che punta ad una variabili intera. Quindi è possibile fare giochi come*a+=1
ma non è possibile fare (semplicemente)a=0xFF0432
;
const
può essere molto utile nelle dichiarazione delle funzioni. Chiaramente usare const
nella dichiarazione dei parametri di funzione di tipo diverso dal puntatore non ha molto senso, siccome sono copie per definizione. Per esempio:
int sum(const int a, const int b) {
return a+b;
}
i const
qua sono inutili dato che sono già copie. Al più possono servire per evitare typo nelle comparazioni:
bool equalsInt(const int a, const int b) {
if (a = b) { //const allows you to intercept this typo!!!
return true;
} else {
return false;
}
}
Ma anche qua, sono caratteri in più! Stessa argomentazione può essere fatta per dichiarazioni di puntatore costanti:
void increase(int* const a) {
*a += 1;
}
Il puntatore è una copia, quindi il const
non aiuta! Citando Patrick:
IMO there's no point to consting the pointer itself (or any other function parameter). The constness of the pointed to area is important, it tells the reader that there are no side effects via this pointer, but the constness of the pointer itself conveys no useful information, neither for compiler nor for the programmer.
Un uso invece molto utile è per rappresentare un puntatore ad un valore costante. Per esempio:
int sumAndIncrease(int* base, const int* increment);
int sumAndIncrease(int* base, const int* increment) {
*base+=*increment;
return *base;
}
Senza nemmeno vedere il body della funzione, un programmatore può immediatamente vedere che la funzione non altera in alcun modo il valore puntato da increment
mentre potrebbe modificare il valore puntato da base
. Se stai creando una libreria puoi usare const
per far capire allo sviluppatore quali valori sono modificati dai puntatori e quali no.
const
non rappresenta una costante. const
dice solo che quella variabile non può essere direttamente modificata.
Considera questo esempio (scritto da Linus):
#include <stdio.h>
int v;
int *p;
void f(const int *);
int main(void) {
p = &v;
f(&v);
return 0;
}
void f(const int *vp) {
int i, j;
i = *vp;
*p = 7;
j = *vp;
printf("%d, %d\n", i, j);
}
nonostante vp
sia un puntatore variabile ad una "costante", i
e j
avranno valori completamente diversi! Ciò è reso possibile dall'aliasing: const
dice solo che l'accesso all'area di memoria tramite la variabile qualificata con const
non permette la modifica, ma nulla viene detto riguardo possibili altri accessi alla stessa area di memoria(nell'esempio di Linus il puntatore p
). Questo è il motivo per cui lo standard ha una definizione di const
che riguarda gli lvalue
.
Sia vp
che p
riferiscono alla stessa area di memoria, solo che accedendo con vp
puoi solo leggere mentre accedendo con p
puoi anche scriverci dentro. Vedi quindi const
come un modo di accesso ristretto all'area di memoria.
Fai in modo che una funzione ritorni const int*
se non vuoi che lo sviluppatore possa modificare la variabile, ritorna int*
se invece gli vuoi dare gli accessi di "root".
Il qualifier const
può essere aggiunto nella definizione di una funzione, anche se esso non era presente nella dichiarazione. Questo perché, a volte, la modifica o meno di un puntatore dipende dall'implementazione stessa. Per esempio la funzione void print_model(const my_big_model* model)
modifica model
? In un'implementazione magari no mentre magari in un'altra viene modificato perché viene popolata una cache nel modello stesso. Mettendo const
solo nell'implementazione pertinente si generalizza la dichiarazione della funzione stessa.
- Linus Torvalds tratta di const;
- Un commento illuminante di Patrick Schlüter;
- Discussione sulla pericolosità del casting da const a non const;
- Quando il casting da const a non const è necessario;
- Perché non si può inizializzare un puntatore ad una costante con un puntatore a variabile?;
- Standard C99 sezione 6.7.3