El calificador volátil en C++

Jinku Hu 12 octubre 2023
El calificador volátil en C++

Este artículo presentará el calificador volatile en C++.

Utilice el calificador volatile para indicar el objeto que se modifica por otro hilo o acción externa en C++

El comportamiento de la palabra clave volatile generalmente debe considerarse como dependiente del hardware, y los desarrolladores de aplicaciones en el espacio del usuario siempre deben consultar los manuales específicos del compilador sobre cómo se puede interpretar el calificador en varios escenarios. Por lo general, la palabra clave volatile notifica al compilador que el objeto dado no debe optimizarse para las operaciones de carga y siempre debe recuperarse de la memoria principal en lugar de los registros o cachés. Tenga en cuenta que hay varias jerarquías de cachés que en su mayoría son inaccesibles para el software y se administran únicamente en el hardware, pero cuando el compilador intenta cargar la ubicación de la memoria en el registro, se almacena automáticamente en caché. Por lo tanto, el acceso consecuente a la misma ubicación de memoria se puede realizar desde las líneas de caché cercanas a la CPU y varias veces más rápido que la RAM.

Mientras tanto, si el objeto es modificado por alguna señal externa o una rutina similar a una interrupción, se debe acceder al valor cambiado desde la RAM, ya que el valor almacenado en caché ya no es válido. Por tanto, los accesos a los objetos volatile son gestionados por el compilador de forma correspondiente. Para demostrar el escenario posible, implementamos una función que modifica la variable entera volatile global y otra función que evalúa el mismo entero en la sentencia del bucle while. Observe que el bucle while puede tener un cuerpo vacío para que funcione este ejemplo. Al principio, la función main crea un hilo independiente que ejecuta la función IncrementSeconds. Inmediatamente después, el hilo principal invoca la función DelayTenSeconds, que entra en el bucle que no volverá si la variable seconds no supera el valor de 10. Dado que el otro subproceso comenzó a incrementar la variable seconds al mismo tiempo, el subproceso principal pronto observará el valor cambiado y regresará de la función.

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

Producción :

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

Como resultado, básicamente implementamos una función de retardo condicional que espera hasta que el objeto volatile sea modificado por una acción externa. Ahora, uno podría notar que este código se comportará exactamente igual si se elimina el calificador volatile y se utiliza la variable global regular, pero no se debe olvidar que el bloque de código de modificación puede ser de una unidad de traducción diferente o acción de señal externa. Los últimos escenarios obligarían al compilador a optimizar el bucle DelayTenSeconds ya que la variable no se modifica en la unidad de traducción actual.

#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