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;
}
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