volatile în C++

Spune compilatorului că valoarea se poate schimba din afara programului, deci fiecare citire și scriere trebuie făcută efectiv.

Sintaxă
volatile int registru;
volatile std::sig_atomic_t semnal_primit = 0;

Ce face volatile

Compilatorul optimizează: dacă vede că o variabilă nu se schimbă într-o buclă, o poate citi o singură dată. volatile interzice asta: fiecare citire și fiecare scriere din cod se face cu adevărat în memorie.

Folosirile corecte sunt puține: registre hardware la microcontrolere (valoarea se schimbă singură), variabile modificate dintr-un handler de semnal (volatile std::sig_atomic_t) și câteva cazuri de măsurare a performanței.

volatile nu face o variabilă sigură între fire de execuție (thread-uri). Pentru asta există std::atomic și std::mutex. Din C++20, operații ca ++ sau += pe variabile volatile sunt considerate învechite (deprecated).

Exemple

Exemplul 1: Variabilă modificată de un handler de semnal

std::raise trimite programului semnalul SIGINT (același ca la Ctrl+C), iar handler-ul setează variabila. Tipul volatile std::sig_atomic_t este cel garantat sigur pentru acest caz.

#include <csignal>
#include <print>

volatile std::sig_atomic_t oprire_ceruta = 0;

void la_semnal(int) {
    oprire_ceruta = 1;
}

int main() {
    std::signal(SIGINT, la_semnal);
    std::println("Înainte: {}", static_cast<int>(oprire_ceruta));
    std::raise(SIGINT);
    std::println("După semnal: {}", static_cast<int>(oprire_ceruta));
    if (oprire_ceruta) {
        std::println("Programul se oprește curat.");
    }
}

Rezultat

Înainte: 0
După semnal: 1
Programul se oprește curat.

Exemplul 2: Citiri și scrieri care nu sunt eliminate

Fără volatile, compilatorul ar putea înlocui cele 1000 de scrieri cu una singură. Cu volatile, fiecare scriere are loc (util, de exemplu, pentru un registru hardware).

#include <print>

int main() {
    volatile int registru = 0;
    for (int i = 0; i < 1000; i = i + 1) {
        registru = i;
    }
    int ultima = registru;
    std::println("Ultima valoare scrisă: {}", ultima);
}

Rezultat

Ultima valoare scrisă: 999

Greșeli frecvente

  • Folosești volatile bool gata ca să comunici între două fire de execuție. Nu este suficient (nu garantează ordinea operațiilor și nici atomicitatea); folosește std::atomic<bool>.
  • Scrii contor++ pe o variabilă volatile. În C++20 primești avertisment de învechire; scrie explicit contor = contor + 1 și gândește-te dacă îți trebuie de fapt std::atomic.

Comenzi înrudite

  • const: Marchează o valoare care nu mai poate fi modificată după inițializare; compilatorul oprește orice încercare de schimbare.
  • asm: Inserează instrucțiuni în limbaj de asamblare direct în codul C++; sintaxa și instrucțiunile depind de compilator și de procesor.
  • thread_local: Fiecare fir de execuție (thread) primește propria copie a variabilei, creată când pornește firul și distrusă la final.
  • std::atomic: Variabilă care poate fi citită și modificată sigur de mai multe fire, fără mutex.