Skip to content

Espressioni Costanti e Volatili (constexpr e volatile)

Il C++ non lascia mai a terra un’ottimizzazione se è possibile eseguirla. Per motivazioni legate alle estreme performance e alla sicurezza, quando un’operazione matematica può essere risolta del tutto a tempo di compilazione (perché non dipende da input a runtime o da API randomiche), il programmatore dovrebbe sfruttarla.

I vecchi programmatori hard-codavano magic values nel codice precalcolati a mano pur di velocizzare le applicazioni. Oggi abbiamo constexpr.

Se tutte le informazioni richieste per calcolare una funzione o inizializzare una variabile sono presenti in sede di compilazione, marcando tale firma con constexpr tu costringi/obblighi tacitamente il compilatore a “eseguirla” come se fosse un piccolo programma nel programma. Nel binario finale, l’espressione o la funzione decadrà trasformandosi direttamente nel valore costante letterale, azzerando qualsiasi sovraccarico in esecuzione.

Oltre all’enorme vantaggio prestativo, permette una leggibilità cristallina della logica di conversione invece di ficcare magic numbers dentro l’editor. Le restrizioni su cosa può contenere il corpo di una funzione constexpr si sono molto allentate nel C++ moderno, ma il presupposto base resta: non deve mai usare funzioni o invocare input impossibili da conoscere durante la compilazione.

(Nota: Ogni constexpr è implicitamente un const perché fissato a runtime, ma un const non è necessariamente constexpr).

La parola chiave volatile ha uno status da primadonna ed è costantemente confusa e abusata, persino dai programmatori navigati, i quali ritengono erroneamente che renda le variabili “thread-safe” proteggendole dalla concorrenza asincrona. (No, quello è il ruolo della libreria std::atomic!).

Il vero scopo di volatile è interloquire con la mente del compilatore-ottimizzatore. L’ottimizzatore è feroce:

int foo() {
int x = 10;
x = 20; // Ottimizzatore: "Il 10 era un dead store inutile, elimino x=10 dal programma."
auto y = x;
y = x; // Ottimizzatore: "Lettura inutile ridondante. Rimuovo."
return y;
}

Queste ottimizzazioni vanno benone sui software PC. Ma su un Microcontrollore o per il Firmware di una centralina? Se stiamo comunicando con i pin hardware dell’oggetto e la variabile “x” mappa il segnale su un Relé o un LED che va “acceso” e subito “spento”? Inibire all’ottimizzatore l’ablazione del “dead store” è cruciale.

Anteponendo volatile a una dichiarazione, urli al compilatore: “Non osare ottimizzare o riordinare le letture o scritture di questa variabile! Anche se ti sembrano inutili ed inefficienti, ogni lettura e ogni scrittura deve materializzarsi rigorosamente a runtime per la macchina, e devono mantenere un visibile e inalterato effetto collaterale al di fuori della logica software!”