Const - STB1019/SkullOfSummer GitHub Wiki

Introduzione

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.

Cosa è const

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 con const non può essere modificata tramite un'operazione che coinvolga lvalue della variabile X stessa.

Per esempio, se const int a=5, allora:

  • a+4 è lecito;
  • a=4; non lo è (perché stiamo modificando la variabile tramite lvalue);

Dichiarazione di una variabile qualificata con const

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 e int 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;

Usi tipici di const

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.

Cosa non è const

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".

Offuscamento dell'implementazione

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.

Riferimenti

⚠️ **GitHub.com Fallback** ⚠️