O qualificador volátil em C++
Este artigo apresentará o qualificador volatile
em C++.
Use o qualificador volatile
para denotar o objeto que é modificado por outro thread ou ação externa em C++
O comportamento da palavra-chave volatile
deve geralmente ser considerado como dependente do hardware, e os desenvolvedores de aplicativos do espaço do usuário devem sempre consultar os manuais do compilador específico sobre como o qualificador pode ser interpretado em vários cenários. Normalmente, a palavra-chave volatile
notifica o compilador que o objeto fornecido não deve ser otimizado para operações de carregamento e sempre recuperado da memória principal em vez dos registradores ou caches. Observe que existem várias hierarquias de caches que são em sua maioria inacessíveis ao software e gerenciadas apenas no hardware, mas quando o compilador tenta carregar o local da memória no registro, ele é automaticamente armazenado em cache. Assim, o acesso consequente ao mesmo local de memória pode ser feito a partir das linhas de cache próximas à CPU e várias vezes mais rápido que a RAM.
Enquanto isso, se o objeto for modificado por algum sinal externo ou uma rotina do tipo interrupção, o valor alterado deve ser acessado da RAM, pois o valor armazenado em cache não é mais válido. Assim, os acessos a objetos volatile
são tratados pelo compilador de forma correspondente. Para demonstrar o cenário possível, implementamos uma função que modifica a variável inteira volatile
global e outra função que avalia o mesmo número inteiro na instrução de loop while
. Observe que o loop while
pode ter um corpo vazio para que este exemplo funcione. Em primeiro lugar, a função main
cria uma thread separada que executa a função IncrementSeconds
. Logo depois disso, o thread principal invoca a função DelayTenSeconds
, que vai para o loop que não retornará se a variável seconds
não exceder o valor de 10
. Como a outra thread começou a incrementar a variável seconds
já simultaneamente, a thread principal logo observará o valor alterado e retornará da função.
#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;
}
Resultado:
waiting...
incremented
waiting...
....
waiting...
10.002481 sec
Como resultado, essencialmente implementamos uma função de atraso condicional que espera até que o objeto volatile
seja modificado por ação externa. Agora, pode-se notar que este código se comportará exatamente da mesma forma se o qualificador volatile
for removido e a variável global regular for utilizada, mas não se deve esquecer que o bloco de código de modificação pode ser de uma unidade de tradução diferente ou ação de sinal externa. Os últimos cenários forçariam o compilador a otimizar o loop DelayTenSeconds
, uma vez que a variável não é modificada na unidade de tradução atual.
#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