it-swarm-es.tech

¿Por qué existe volatile?

¿Qué hace la palabra clave volatile? En C++ ¿Qué problema resuelve?

En mi caso, nunca lo he necesitado a sabiendas.

194
theschmitzer

Se necesita volatile si está leyendo de un lugar en la memoria que, digamos, un proceso/dispositivo/dispositivo completamente independiente, en el que pueda escribir.

Solía ​​trabajar con un ram de doble puerto en un sistema multiprocesador en línea recta C. Usamos un valor de 16 bits administrado por hardware como un semáforo para saber cuándo se hizo el otro tipo. Esencialmente hicimos esto:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Sin volatile, el optimizador ve el ciclo como inútil (¡el tipo nunca establece el valor! ¡Está loco, quita ese código!) Y mi código continuaría sin haber adquirido el semáforo, lo que causaría problemas más adelante.

239
Doug T.

volatile es necesaria cuando se desarrollan sistemas embebidos o controladores de dispositivos, donde se necesita leer o escribir un dispositivo de hardware mapeado en memoria. El contenido de un registro de dispositivo en particular podría cambiar en cualquier momento, por lo que necesita la palabra clave volatile para asegurarse de que el compilador no optimice dichos accesos.

77
ChrisN

Algunos procesadores tienen registros de punto flotante que tienen más de 64 bits de precisión (por ejemplo, x86 de 32 bits sin SSE, vea el comentario de Peter). De esa manera, si ejecuta varias operaciones con números de precisión doble, obtendrá una respuesta de mayor precisión que si tuviera que truncar cada resultado intermedio a 64 bits.

Esto generalmente es bueno, pero significa que dependiendo de cómo el compilador asignó registros e hizo optimizaciones, tendrá resultados diferentes para las mismas operaciones exactas en las mismas entradas. Si necesita coherencia, puede obligar a cada operación a volver a la memoria usando la palabra clave volátil.

También es útil para algunos algoritmos que no tienen sentido algebraico pero reducen el error de punto flotante, como la suma de Kahan. Algebraicamente es un nop, por lo que a menudo se optimiza incorrectamente a menos que algunas variables intermedias sean volátiles.

68
tfinniga

De a "El volátil como promesa" artículo de Dan Saks:

(...) Un objeto volátil es aquel cuyo valor puede cambiar espontáneamente. Es decir, cuando declara que un objeto es volátil, le está diciendo al compilador que el objeto podría cambiar de estado aunque no haya ninguna declaración en el programa que lo cambie ".

Aquí hay enlaces a tres de sus artículos sobre la palabra clave volatile:

44
MikeZ

DEBE usar volatile cuando implemente estructuras de datos sin bloqueo. De lo contrario, el compilador es libre de optimizar el acceso a la variable, lo que cambiará la semántica.

Para decirlo de otra manera, volatile le dice al compilador que los accesos a esta variable deben corresponder a una operación de lectura/escritura en la memoria física.

Por ejemplo, así es como se declara InterlockedIncrement en la API de Win32:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);
23
Frederik Slijkerman

Una gran aplicación en la que solía trabajar a principios de la década de 1990 contenía el manejo de excepciones basado en C utilizando setjmp y longjmp. La palabra clave volátil era necesaria en las variables cuyos valores debían conservarse en el bloque de código que servía como cláusula de "captura", para que esas vars no se almacenaran en registros y fueran eliminadas por longjmp.

10
Jeff Doar

En el Estándar C, uno de los lugares para usar volatile es con un manejador de señales. De hecho, en el Estándar C, todo lo que puede hacer con seguridad en un controlador de señales es modificar una variable volatile sig_atomic_t, o salir rápidamente. De hecho, AFAIK, es el único lugar en el Estándar C en el que se requiere el uso de volatile para evitar comportamientos indefinidos.

ISO/IEC 9899: 2011 §7.14.1.1 La función signal

¶5 Si la señal se produce de otra manera que no sea el resultado de llamar a la función abort o raise, el comportamiento no está definido si el manejador de señal se refiere a cualquier objeto con una duración de almacenamiento de estática o subproceso que no sea un objeto atómico sin bloqueo, aparte de la asignación de un valor para un objeto declarado como volatile sig_atomic_t, o el manejador de señales llama a cualquier función en la biblioteca estándar que no sea la función abort, la función _Exit, la función quick_exit, o la función signal con el primer argumento igual al número de señal correspondiente al Señal que provocó la invocación del manejador. Además, si tal llamada a la función signal resulta en un retorno SIG_ERR, el valor de errno es indeterminado.252)

252) Si alguna señal es generada por un manejador de señal asíncrono, el comportamiento es indefinido.

Eso significa que en el Estándar C, puedes escribir:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

y no mucho mas.

POSIX es mucho más indulgente con respecto a lo que puede hacer en un controlador de señales, pero aún existen limitaciones (y una de las limitaciones es que la biblioteca de E/S estándar - printf() et al - no se puede usar de manera segura).

10
Jonathan Leffler

Además de usarlo según lo previsto, volatile se usa en la metaprogramación (plantilla). Se puede usar para evitar sobrecargas accidentales, ya que el atributo volátil (como const) participa en la resolución de sobrecargas.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

Esto es legal; ambas sobrecargas son potencialmente exigibles y hacen casi lo mismo. El reparto en la sobrecarga de volatile es legal ya que sabemos que la barra no pasará una T no volátil de todos modos. Sin embargo, la versión volatile es estrictamente peor, por lo que nunca se elige en la resolución de sobrecarga si la f no volátil está disponible.

Tenga en cuenta que el código nunca depende realmente del acceso a la memoria de volatile.

7
MSalters

Lo he usado en versiones de depuración cuando el compilador insiste en optimizar una variable que quiero poder ver a medida que paso por el código.

7
indentation

Desarrollando para un incrustado, tengo un bucle que comprueba una variable que se puede cambiar en un controlador de interrupción. Sin "volátil", el bucle se convierte en un noop; por lo que el compilador puede decir, la variable nunca cambia, por lo que optimiza la comprobación.

Lo mismo se aplicaría a una variable que puede cambiarse en un subproceso diferente en un entorno más tradicional, pero a menudo hacemos llamadas de sincronización, por lo que el compilador no es tan libre con la optimización.

7
Arkadiy
  1. debe usarlo para implementar spinlocks, así como algunas (¿todas?) estructuras de datos sin bloqueo
  2. utilízalo con operaciones atómicas/instrucciones.
  3. una vez me ayudó a superar el error del compilador (código generado incorrectamente durante la optimización)
6
Mladen Janković

La palabra clave volatile está pensada para evitar que el compilador aplique cualquier optimización en los objetos que puedan cambiar de una manera que el compilador no pueda determinar.

Los objetos declarados como volatile se omiten de la optimización porque sus valores pueden ser modificados por código fuera del alcance del código actual en cualquier momento. El sistema siempre lee el valor actual de un objeto volatile desde la ubicación de la memoria en lugar de mantener su valor en el registro temporal en el punto en que se solicita, incluso si una instrucción previa solicitó un valor al mismo objeto.

Considere los siguientes casos

1) Variables globales modificadas por una rutina de servicio de interrupción fuera del alcance.

2) Variables globales dentro de una aplicación multi-hilo.

Si no utilizamos un calificador volátil, pueden surgir los siguientes problemas

1) Es posible que el código no funcione como se espera cuando la optimización está activada.

2) Es posible que el código no funcione como se esperaba cuando las interrupciones se habilitan y usan.

Volatile: el mejor amigo de un programador

https://en.wikipedia.org/wiki/Volatile_(computer_programming)

4
roottraveller

¿Parece que su programa funciona incluso sin la palabra clave volatile? Quizás esta sea la razón:

Como se mencionó anteriormente, la palabra clave volatile ayuda en casos como

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

Pero parece que casi no hay efecto una vez que se llama a una función externa o no en línea. P.ej.:

while( *p!=0 ) { g(); }

Luego, con o sin volatile casi se genera el mismo resultado.

Siempre que g() esté completamente en línea, el compilador puede ver todo lo que está pasando y, por lo tanto, puede optimizar. Pero cuando el programa realiza una llamada a un lugar donde el compilador no puede ver lo que está sucediendo, ya no es seguro que el compilador haga suposiciones. Por lo tanto, el compilador generará código que siempre se lee directamente de la memoria.

Pero tenga cuidado con el día, cuando su función g() se convierte en línea (ya sea debido a cambios explícitos o debido a la habilidad del compilador/enlazador), entonces su código podría romperse si olvidó la palabra clave volatile.

Por lo tanto, recomiendo agregar la palabra clave volatile incluso si su programa parece funcionar sin él. Hace que la intención sea más clara y más robusta con respecto a los cambios futuros.

2
Joachim

En los primeros días de C, los compiladores interpretarían todas las acciones que leen y escriben valores de l como operaciones de memoria, que se realizarán en la misma secuencia en que aparecen las lecturas y escrituras en el código. La eficiencia podría mejorarse mucho en muchos casos si a los compiladores se les diera cierta libertad para reordenar y consolidar las operaciones, pero había un problema con esto. Incluso las operaciones a menudo se especificaban en un cierto orden simplemente porque era necesario especificarlas en algo orden, y por lo tanto el programador escogió una de las muchas alternativas igualmente buenas, que no siempre fue así. A veces sería importante que ciertas operaciones ocurran en una secuencia particular.

Exactamente qué detalles de secuencia son importantes variarán según la plataforma de destino y el campo de aplicación. En lugar de proporcionar un control particularmente detallado, el Estándar optó por un modelo simple: si una secuencia de accesos se realiza con valores que no están calificados volatile, un compilador puede reordenarlos y consolidarlos como lo considere oportuno. Si se realiza una acción con un volatile- valor calificado, una implementación de calidad debe ofrecer cualquier garantía de pedido adicional que pueda requerir el código que apunta a su plataforma y campo de aplicación, sin tener que requerir el uso de una sintaxis no estándar.

Desafortunadamente, en lugar de identificar qué garantías necesitarían los programadores, muchos compiladores optaron por ofrecer las garantías mínimas establecidas por la Norma. Esto hace que volatile sea mucho menos útil de lo que debería ser. En gcc o clang, por ejemplo, un programador que necesita implementar un "mutex de traspaso" básico [uno donde una tarea que ha adquirido y liberado un mutex no lo hará de nuevo hasta que la otra tarea lo haya hecho] debe hacer uno de cuatro cosas:

  1. Coloque la adquisición y el lanzamiento de la exclusión mutua en una función que el compilador no pueda en línea y en la que no pueda aplicar la optimización de todo el programa.

  2. Califique todos los objetos protegidos por el mutex como volatile-- algo que no debería ser necesario si todos los accesos ocurren después de adquirir el mutex y antes de liberarlo.

  3. Use el nivel de optimización 0 para forzar al compilador a generar código como si todos los objetos que no están calificados register sean volatile.

  4. Utilice directivas específicas de gcc.

Por el contrario, cuando se usa un compilador de mayor calidad que es más adecuado para la programación de sistemas, como icc, uno tendría otra opción:

  1. Asegúrese de que se realice una escritura calificada volatile en cualquier lugar donde se necesite una adquisición o liberación.

La adquisición de un "mutex de traspaso" básico requiere una volatile lectura (para ver si está lista), y no debería requerir una volatile escritura también (la otra parte no intentará volver a adquirirla hasta que se la devuelva), pero tener que realizar una escritura volatile sin sentido es aún mejor que cualquiera de las opciones disponibles en gcc o clang.

2
supercat

Además del hecho de que la palabra clave volátil se usa para decirle al compilador que no optimice el acceso a alguna variable (que puede ser modificada por un hilo o una rutina de interrupción), también puede ser utilizada para eliminar algunos errores del compilador - SÍ puede ser ---.

Por ejemplo, trabajé en una plataforma integrada donde el compilador estaba haciendo algunas suposiciones erróneas con respecto al valor de una variable. Si el código no estuviera optimizado, el programa se ejecutaría bien. Con las optimizaciones (que realmente eran necesarias porque era una rutina crítica) el código no funcionaría correctamente. La única solución (aunque no muy correcta) fue declarar la variable "defectuosa" como volátil.

2
INS

Un uso que debo recordar es que, en la función del manejador de señales, si desea acceder/modificar una variable global (por ejemplo, marcarlo como exit = true), debe declarar esa variable como "volátil".

1
bugs king