O qualificador volátil em C++

Jinku Hu 12 outubro 2023
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;
}
Autor: 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