Saltar a contenido

Última actualización:2026-10-12

La palabra clave volatile en C++

Sobre el autor:

Cuenta pública personal:

wechat

1. volatile

Las variables modificadas con volatile, al ser leídas o escritas, provocan algunos efectos laterales observables. Y esos efectos laterales observables están determinados por factores externos al programa.

2. Aplicaciones de volatile

(1) Registros de hardware de dispositivos en paralelo (por ejemplo, el registro de estado). Supongamos que queremos inicializar un dispositivo cuyo registro está en 0xff800000.

int  *output = (unsigned  int *)0xff800000; //定义一个IO端口;  
int   init(void)  
{  
    int i;  
    for(i=0;i< 10;i++)
    {  
    *output = i;  
    }  
}
Tras la optimización del compilador, este considera que todo el bucle anterior es superfluo y no afecta en nada al resultado final, porque al final solo se asigna el valor 9 al puntero output, de modo que el código compilado equivale a:
int  init(void)  
{  
    *output = 9;  
}
Si el proceso de inicialización de ese dispositivo externo exige asignar los valores secuencialmente como en el código anterior, está claro que la optimización no consigue el objetivo. Y si en lugar de escribir repetidamente en ese puerto lo que se hace es leer repetidamente, el resultado es el mismo: tras optimizar, puede que el compilador deje solo una lectura de esa dirección. Sin embargo, desde el punto de vista del código no hay ningún problema. Es entonces cuando hay que usar volatile para notificar al compilador de que esta variable es inestable y que no la optimice al encontrarla.

(2) Variables a las que se accede desde una rutina de servicio de interrupción;

static int i=0;

int main()
{
    while(1)
    {
    if(i) dosomething();
    }
}

/* Interrupt service routine */
void IRS()
{
    i=1;
}
La intención del programa de ejemplo anterior es que, al producirse una interrupción, la rutina de servicio IRS la atienda y modifique la variable i del programa para que se invoque la función dosomething en main; pero como el compilador determina que i no se modifica dentro de main, puede ejecutar solo una lectura de i hacia cierto registro y luego usar esa "copia de i" del registro en cada comprobación if, con lo cual dosomething no se invocaría nunca. Si a la variable i se le añade el modificador volatile, el compilador garantiza que las operaciones de lectura y escritura de i no se optimicen, asegurando así que, cuando i sea modificada por un programa externo, el programa original lo perciba a tiempo.

(3) Variables compartidas por varias tareas en aplicaciones multihilo. Cuando varios hilos comparten una variable cuyo valor puede ser cambiado por cualquiera de ellos, debe declararse con volatile. Su función es impedir que el compilador, al optimizar, cargue la variable de la memoria a un registro de la CPU, lo que provocaría errores si un hilo cambia la variable y no se sincroniza a tiempo con los demás hilos. volatile significa que el compilador, en cada operación sobre esa variable, debe extraerla realmente de la memoria y no usar el valor que ya esté en un registro. Un ejemplo:

volatile  bool bStop=false;  //bStop 为共享全局变量  
//第一个线程
void threadFunc1()
{
    ...
    while(!bStop){...}
}
//第二个线程终止上面的线程循环
void threadFunc2()
{
    ...
    bStop = true;
}
Para terminar el bucle del primer hilo desde el segundo hilo, si bStop no se define con volatile, ese bucle será infinito, porque bStop ya se habrá leído a un registro y el valor de bStop en el registro jamás pasará a ser FALSE; con volatile, el programa, en cada iteración, lee el valor de bStop desde la memoria y así el bucle no se vuelve infinito.

Saber cuáles son los escenarios de aplicación de volatile es una forma eficaz de distinguir a un programador de C/C++ de un programador de desarrollo embebido: los que trabajan en embebidos tratan a menudo con hardware, interrupciones, RTOS, etc., y todo ello exige usar variables volatile; no entender volatile trae consigo desastres en el diseño de programas.

3. Preguntas frecuentes sobre volatile

Con las siguientes preguntas se puede ver si un candidato entiende de verdad volatile. (1) ¿Puede un parámetro ser a la vez const y volatile? ¿Por qué? Sí. Un ejemplo es un registro de estado de solo lectura. Es volatile porque puede cambiar de forma inesperada. Es const porque el programa no debería intentar modificarlo.

(2) ¿Puede un puntero ser volatile? ¿Por qué? Sí, aunque no es habitual. Un ejemplo es cuando una rutina de servicio de interrupción modifica un puntero que apunta a un buffer.

(3) ¿Qué error tiene la siguiente función?

int square(volatile int *ptr) 
{ 
return *ptr * *ptr; 
} 
Este código es un poco retorcido: su propósito es devolver el cuadrado del valor apuntado por el puntero ptr, pero como ptr apunta a un parámetro de tipo volatile, el compilador generará algo como lo siguiente:
int square(volatile int *ptr) 
{ 
int a,b; 
a = *ptr; 
b = *ptr; 
return a * b; 
} 
Como el valor de *ptr puede cambiar de forma inesperada, a y b pueden ser distintos. En consecuencia, ¡este código puede no devolver el cuadrado que esperas! El código correcto es el siguiente:
long square(volatile int *ptr) 
{ 
int a=*ptr; 
return a * a; 
} 

4. Uso de volatile

  • La palabra clave volatile es un modificador de tipo; las variables del tipo declarado con ella indican que pueden ser cambiadas por factores desconocidos para el compilador (sistema operativo, hardware, otros hilos, etc.). Por eso, usar volatile indica al compilador que no debe optimizar dichos objetos.

  • Las variables declaradas con la palabra clave volatile deben extraer su valor de la memoria en cada acceso (las variables no modificadas con volatile pueden, por la optimización del compilador, tomar su valor de un registro de la CPU)

  • const puede ser volatile (por ejemplo, un registro de estado de solo lectura)
  • Un puntero puede ser volatile

Código para estudiar:

Temas relacionados

Comentarios