c++ - ejemplos - meta tags seo
¿Por qué existe la volatilidad? (15)
- debe usarlo para implementar estructuras de datos spinlocks y algunas (¿todas?) estructuras libres de bloqueo
- Úselo con operaciones / instrucciones atómicas
- me ayudó una vez a superar el error del compilador (código generado incorrectamente durante la optimización)
¿Qué hace la palabra clave volatile
? En C ++, ¿qué problema soluciona?
En mi caso, nunca lo había necesitado a sabiendas.
Además de usarlo como se pretende, volátil se utiliza en metaprogramación (plantilla). Se puede usar para evitar sobrecargas accidentales, ya que el atributo volátil (como const) toma parte en la resolución de sobrecarga.
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 invocables y hacen casi lo mismo. El reparto en la sobrecarga volatile
es legal ya que sabemos que la barra no pasará una T
no volátil de todos modos. 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 en realidad nunca depende del acceso a la memoria volatile
.
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 se puede usar para eliminar algunos errores del compilador : SÍ puede ser ---.
Por ejemplo, trabajé en una plataforma incrustada donde el compilador realizaba algunas sugestiones erróneas con respecto al valor de una variable. Si el código no se optimizó, el programa se ejecutará correctamente. Con las optimizaciones (que realmente se necesitaban 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.
DEBE usar volátil cuando implemente estructuras de datos sin bloqueo. De lo contrario, el compilador puede optimizar el acceso a la variable, lo que cambiará la semántica.
Para decirlo de otra manera, volátil le dice al compilador que los accesos a esta variable deben corresponder a una operación de lectura / escritura de la memoria física.
Por ejemplo, así es como se declara InterlockedIncrement en la API de Win32:
LONG __cdecl InterlockedIncrement(
__inout LONG volatile *Addend
);
De un artículo de Systems Embedded de Dan Saks:
"Un objeto volátil es aquel cuyo valor puede cambiar espontáneamente. Es decir, cuando declaras que un objeto es volátil, le estás diciendo al compilador que el objeto podría cambiar de estado aunque no haya declaraciones en el programa que lo modifiquen".
Enlaces a 2 excelentes artículos del Sr. Saks sobre la palabra clave volátil:
http://www.embedded.com/columns/programmingpointers/174300478 http://www.embedded.com/columns/programmingpointers/175801310
Desarrollando para un incrustado, tengo un ciclo que verifica una variable que se puede cambiar en un manejador de interrupciones. 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 el control.
Lo mismo se aplicaría a una variable que puede cambiarse en un hilo diferente en un entorno más tradicional, pero a menudo hacemos llamadas de sincronización, por lo que el compilador no está tan libre de optimización.
En el Estándar C, uno de los lugares para usar volatile
es con un manejador de señal. De hecho, en el Estándar C, todo lo que puede hacer con seguridad en un controlador de señal es modificar una variable volatile sig_atomic_t
o salir rápidamente. De hecho, AFAIK, es el único lugar en el Estándar C que se requiere el uso de volatile
para evitar el comportamiento indefinido.
ISO / IEC 9899: 2011 §7.14.1.1 La función de
signal
¶5 Si la señal no es el resultado de llamar a la función
abort
oraise
, el comportamiento no está definido si el manejador de señal se refiere a cualquier objeto con duración estática o de almacenamiento de subproceso que no sea un objeto atómico sin bloqueo que no sea la asignación un valor para un objeto declarado comovolatile sig_atomic_t
, o el manejador de señal llama a cualquier función en la biblioteca estándar distinta de la funciónabort
, la función_Exit
, la funciónquick_exit
o la función designal
con el primer argumento igual al número de señal correspondiente a la señal que provocó la invocación del controlador. Además, si tal llamada a la función designal
resulta en un retorno SIG_ERR, el valor deerrno
es indeterminado. 252)252) Si cualquier señal es generada por un manejador de señal asíncrono, el comportamiento no está definido.
Eso significa que en el Estándar C, puede 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 más.
POSIX es mucho más indulgente con respecto a lo que puede hacer en un manejador de señal, pero todavía existen limitaciones (y una de las limitaciones es que la biblioteca de E / S estándar - printf()
y otros) no se puede usar de manera segura).
La mayoría de los procesadores modernos tienen registros de coma flotante que tienen más de 64 bits de precisión. De esta forma, si ejecuta varias operaciones con números de precisión doble, obtendrá una respuesta de mayor precisión que si truncara cada resultado intermedio a 64 bits.
Esto suele ser genial, pero significa que, dependiendo de cómo el compilador haya asignado registros y optimizaciones, tendrá resultados diferentes para las mismas operaciones en las mismas entradas exactas. Si necesita coherencia, puede forzar a cada operación a volver a la memoria utilizando 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 optimizará incorrectamente a menos que algunas variables intermedias sean volátiles.
La palabra clave volatile
está destinada a evitar que el compilador aplique optimizaciones en los objetos que pueden cambiar de formas que el compilador no puede determinar.
Los objetos declarados como volatile
se omiten de la optimización porque sus valores se pueden cambiar 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 del 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 multiproceso.
Si no usamos el calificador volátil, pueden surgir los siguientes problemas
1) Es posible que el código no funcione como se espera cuando se activa la optimización.
2) Es posible que el código no funcione como se espera cuando las interrupciones están habilitadas y utilizadas.
Volátil: el mejor amigo de un programador
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
Lo he usado en compilaciones de depuración cuando el compilador insiste en optimizar una variable que deseo poder ver mientras paso por el código.
Su programa parece funcionar incluso sin palabras clave volatile
? Tal vez esta es 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(); }
Entonces con o sin volatile
se genera casi el mismo resultado.
Siempre que g () pueda estar completamente alineado, el compilador puede ver todo lo que está ocurriendo y, por lo tanto, puede optimizar. Pero cuando el programa hace una llamada a un lugar donde el compilador no puede ver lo que está sucediendo, no es seguro para el compilador hacer más suposiciones. Por lo tanto, el compilador generará código que siempre lee directamente de la memoria.
Pero ten cuidado con el día, cuando tu función g () se vuelva en línea (ya sea debido a cambios explícitos o debido a la inteligencia del compilador / enlazador), entonces tu código podría romperse si olvidaste 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 sólida con respecto a los cambios futuros.
Un uso que debo recordar es que, en la función del manejador de señal, si desea acceder / modificar una variable global (por ejemplo, marcarla como exit = true) debe declarar esa variable como ''volátil''.
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 para las variables cuyos valores debían conservarse en el bloque de código que servía como la cláusula "catch", para evitar que esos vars se almacenaran en registros y desaparecieran por longjmp.
volatile
es necesario al desarrollar sistemas integrados o controladores de dispositivos, donde 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 garantizar que el compilador no optimice dichos accesos.
volatile
es necesario si está leyendo desde un punto en la memoria que, digamos, un proceso / dispositivo completamente separado / lo que sea que escriba.
Solía trabajar con ram de doble puerto en un sistema de multiprocesador en C. Recurrí a un valor de 16 bits administrado por hardware como semáforo para saber cuándo había terminado 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 hombre nunca establece el valor! ¡Está loco, se deshace de ese código!) Y mi código procedería sin haber adquirido el semáforo, causando problemas más adelante.