1. ¿Por qué usar volatile?

La palabra clave volatile en C/C++ se corresponde con const, se usa para modificar variables, y generalmente se utiliza para establecer una barrera de memoria (memory barrier) a nivel de lenguaje. Esto es lo que BS explica sobre el modificador volatile en 'The C++ Programming Language':

A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.

La palabra clave volatile es un modificador de tipo. Las variables de tipo declaradas con ella indican que pueden ser modificadas por factores desconocidos para el compilador, como el sistema operativo, el hardware u otros hilos, etc. Cuando se encuentra una variable declarada con esta palabra clave, el compilador ya no optimiza el código que accede a dicha variable, proporcionando así un acceso estable a direcciones especiales. La sintaxis al declararla es:int volatile vInt;Cuando se requiere el valor de una variable declarada como volatile, el sistema siempre vuelve a leer los datos desde la memoria donde se encuentra, incluso si la instrucción anterior acaba de leer los datos de ese lugar. Además, los datos leídos se guardan inmediatamente. Por ejemplo:

volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;

volatile indica que i puede cambiar en cualquier momento; cada vez que se use, debe leerse desde la dirección de i, por lo que el código ensamblador generado por el compilador volverá a leer los datos desde la dirección de i y los colocará en b. En cambio, la optimización hace que, como el compilador detecta que entre dos lecturas de i no hay ninguna operación sobre i, automáticamente coloque los datos leídos anteriormente en b, en lugar de volver a leerlos desde i. De este modo, si i es una variable de registro o representa datos de un puerto, es fácil que ocurran errores. Por lo tanto, volatile puede garantizar un acceso estable a direcciones especiales. Tenga en cuenta que en VC 6, el modo de depuración generalmente no realiza optimización de código, por lo que no se puede ver el efecto de esta palabra clave. A continuación, insertando código ensamblador, probemos la influencia de tener o no la palabra clave volatile en el código final del programa, introduciendo el siguiente código:

Ejemplo

#include <stdio.h> void main() { int i = 10; int a = i; printf("i = %d", a); // La función de la siguiente instrucción ensambladora es cambiar el valor de i en la memoria // pero sin que el compilador lo sepa __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b); }

Luego, ejecute el programa en modo Debug, el resultado es el siguiente:

i = 10
i = 32

Luego, ejecute el programa en modo Release, el resultado es el siguiente:

i = 10
i = 10

El resultado muestra claramente que en modo Release, el compilador optimizó el código y la segunda vez no se generó el valor correcto de i. A continuación, agreguemos la palabra clave volatile a la declaración de i y veamos qué cambia:

Ejemplo

#include <stdio.h> void main() { volatile int i = 10; int a = i; printf("i = %d", a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b); }

Ejecute el programa en las versiones Debug y Release respectivamente, el resultado en ambos casos es:

i = 10
i = 32

Esto demuestra que la palabra clave volatile ha cumplido su función. En realidad, no solo el método de 'manipular la pila con ensamblador embebido' es un cambio de variable que el compilador no puede reconocer; además, es más común que, cuando múltiples hilos acceden concurrentemente a una variable compartida, un hilo cambie el valor de la variable. ¿Cómo hacer que el valor cambiado sea visible para otros hilos? En general, volatile se utiliza en los siguientes lugares:

  • 1) Las variables modificadas en el programa de servicio de interrupción y utilizadas para que otros programas las detecten deben declararse como volatile;
  • 2) En un entorno multitarea, los indicadores compartidos entre tareas deben declararse como volatile;
  • 3) Los registros de hardware mapeados en memoria generalmente también deben declararse como volatile, porque cada lectura/escritura puede tener un significado diferente.

2. Puntero volatile

Similar al modificador const, const tiene los conceptos de puntero a constante y puntero constante, volatile también tiene conceptos correspondientes:

Modifica al objeto apuntado por el puntero; los datos son const o volatile:

const char* cpch;
volatile char* vpch;

Nota: Para VC, esta característica solo es segura después de VC 8.

El valor del propio puntero —una variable entera que representa una dirección— es const o volatile:

char* const pchc;
char* volatile pchv;

Nota:

  • (1) Se puede asignar un int no volatile a un int volatile, pero no se puede asignar un objeto no volatile a un objeto volatile.
  • (2) Además de los tipos básicos, los tipos definidos por el usuario también pueden modificarse con el tipo volatile.
  • (3) En C++, una clase con el identificador volatile solo puede acceder a un subconjunto de su interfaz, un subconjunto controlado por el implementador de la clase. El usuario solo puede usar const_cast para obtener acceso completo a la interfaz del tipo. Además, volatile, al igual que const, se transmite de la clase a sus miembros.

3. volatile en múltiples hilos

Algunas variables se declaran con la palabra clave volatile. Cuando dos hilos van a utilizar una misma variable y el valor de dicha variable va a ser cambiado, se debe declarar como volatile. La función de esta palabra clave es evitar que el compilador optimizador cargue la variable desde la memoria a un registro de la CPU. Si la variable se carga en un registro, entonces es posible que un hilo use la variable en memoria y el otro use la variable en el registro, lo que provocará una ejecución incorrecta del programa. volatile significa que cada vez que el compilador opera sobre la variable, debe obtenerla realmente desde la memoria, en lugar de usar el valor que ya existe en el registro, como sigue:

volatile  BOOL  bStop  =  FALSE;

(1) En un hilo:

while(  !bStop  )  {  ...  }  
bStop  =  FALSE;  
return;    

(2) En otro hilo, para terminar el bucle del hilo anterior:

bStop  =  TRUE;  
while(  bStop  );  //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。

Esta palabra clave se utiliza para establecer que la ubicación de almacenamiento de un objeto esté en la memoria, no en un registro. Porque para los objetos normales, el compilador puede colocar su copia en un registro para acelerar la ejecución de las instrucciones, por ejemplo, en el siguiente fragmento de código:

...  
int  nMyCounter  =  0;  
for(;  nMyCounter<100;nMyCounter++)  
{  
...  
}  
...

En este fragmento de código, la copia de nMyCounter puede almacenarse en algún registro (en el bucle, la prueba y la operación sobre nMyCounter siempre se realizan sobre el valor en este registro), pero además hay otro fragmento de código que ejecuta una operación así:nMyCounter -= 1;En esta operación, el cambio de nMyCounter se realiza sobre el nMyCounter en memoria, por lo que aparece el fenómeno de que los cambios de nMyCounter no están sincronizados.

Dirección original: https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html