Il qualificatore volatile in C++

Jinku Hu 12 ottobre 2023
Il qualificatore volatile in C++

Questo articolo introdurrà il qualificatore volatile in C++.

Usa il qualificatore volatile per indicare l’oggetto che viene modificato da un altro thread o azione esterna in C++

Il comportamento della parola chiave volatile dovrebbe essere generalmente considerato come dipendente dall’hardware e gli sviluppatori di applicazioni nello spazio utente dovrebbero sempre consultare i manuali specifici del compilatore su come il qualificatore può essere interpretato in vari scenari. Solitamente la parola chiave volatile avvisa il compilatore che l’oggetto dato non deve essere ottimizzato per le operazioni di caricamento e recuperato sempre dalla memoria principale invece che dai registri o dalle cache. Si noti che ci sono più gerarchie di cache che sono per lo più inaccessibili al software e gestite esclusivamente nell’hardware, ma quando il compilatore tenta di caricare la posizione di memoria nel registro, viene automaticamente memorizzata nella cache. Pertanto, l’accesso conseguente alla stessa posizione di memoria può essere effettuato dalle linee di cache vicine alla CPU e più volte più veloci della RAM.

Nel frattempo, se l’oggetto viene modificato da un segnale esterno o da una routine di tipo interrupt, è necessario accedere al valore modificato dalla RAM poiché il valore memorizzato nella cache non è più valido. Pertanto, gli accessi agli oggetti volatile sono gestiti dal compilatore in modo corrispondente. Per dimostrare il possibile scenario, implementiamo una funzione che modifica la variabile intera globale volatile e un’altra funzione che valuta lo stesso intero nell’istruzione del cicli while. Nota che il cicli while può avere un corpo vuoto affinché questo esempio funzioni. All’inizio, la funzione main crea un thread separato che esegue la funzione IncrementSeconds. Subito dopo, il thread principale invoca la funzione DelayTenSeconds, che entra nel bucle che non tornerà se la variabile seconds non supera il valore di 10. Poiché l’altro thread ha già iniziato ad incrementare la variabile seconds contemporaneamente, il thread principale osserverà presto il valore modificato e tornerà dalla funzione.

#include <unistd.h>

#include <iostream>
#include <thread>

using std::cerr;
using std::cin;
using std::cout;
using std::endl;

volatile int seconds = 0;

void DelayTenSeconds() {
  while (seconds < 10) {
    usleep(500000);
    cerr << "waiting..." << endl;
  }
}

void IncrementSeconds() {
  for (int i = 0; i < 10; ++i) {
    sleep(1);
    cerr << "incremented " << endl;
    seconds = seconds + 1;
  }
}

int main() {
  struct timeval start {};
  struct timeval end {};
  std::thread th1;

  th1 = std::thread(IncrementSeconds);

  DelayTenSeconds();

  th1.join();
  return EXIT_SUCCESS;
}

Produzione:

waiting...
incremented
waiting...
....
waiting...
10.002481 sec

Di conseguenza, abbiamo essenzialmente implementato una funzione di ritardo condizionale che attende che l’oggetto volatile venga modificato da un’azione esterna. Ora, si potrebbe notare che questo codice si comporterà esattamente allo stesso modo se il qualificatore volatile viene rimosso e viene utilizzata una variabile globale regolare, ma non bisogna dimenticare che il blocco del codice di modifica potrebbe provenire da un’unità di traduzione diversa o da un’azione del segnale esterno. Questi ultimi scenari costringerebbero il compilatore a ottimizzare il bucle DelayTenSeconds poiché la variabile non viene modificata nell’unità di traduzione corrente.

#include <sys/time.h>
#include <unistd.h>

#include <iostream>
#include <thread>

using std::cerr;
using std::cin;
using std::cout;
using std::endl;

volatile int seconds = 0;

void DelayTenSeconds() {
  while (seconds < 10) {
    usleep(500000);
    cerr << "waiting..." << endl;
  }
}

float TimeDiff(struct timeval *start, struct timeval *end) {
  return (end->tv_sec - start->tv_sec) + 1e-6 * (end->tv_usec - start->tv_usec);
}

void IncrementSeconds() {
  for (int i = 0; i < 10; ++i) {
    sleep(1);
    cerr << "incremented " << endl;
    seconds = seconds + 1;
  }
}

int main() {
  struct timeval start {};
  struct timeval end {};
  std::thread th1;

  th1 = std::thread(IncrementSeconds);

  gettimeofday(&start, nullptr);
  DelayTenSeconds();
  gettimeofday(&end, nullptr);

  printf("%0.6f sec\n", TimeDiff(&start, &end));

  th1.join();
  return EXIT_SUCCESS;
}
Autore: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook