Existe una técnica intrigante que hace que su compilador detecte posibles condiciones de carrera que se basan en gran medida en la palabra clave volátil, puede leer sobre ella en http://www.ddj.com/cpp/184403766 .
Lo uso para el código sin bloqueo / bloqueo de doble verificación
Paul
Para mí, volatilemás útil que la friendpalabra clave.
acegs
Respuestas:
268
volatile es necesario si está leyendo desde un punto en la memoria que, por ejemplo, un proceso / dispositivo / lo que sea completamente separado en lo que pueda escribir.
Solía trabajar con ram de doble puerto en un sistema multiprocesador en línea C. Usamos 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(){volatileuint16_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, deshazte de ese código!) Y mi código continuaría sin haber adquirido el semáforo, causando problemas más adelante.
En este caso, ¿qué pasaría si uint16_t* volatile semPtrse escribiera en su lugar? Esto debería marcar el puntero como volátil (en lugar del valor señalado), de modo que las comprobaciones en el puntero en sí, por ejemplo semPtr == SOME_ADDR, no puedan optimizarse. Sin embargo, esto también implica un valor puntiagudo volátil nuevamente. ¿No?
Zyl
@Zyl No, no lo hace. En la práctica, lo que sugiere es lo que sucederá. Pero teóricamente, uno podría terminar con un compilador que optimiza el acceso a los valores porque decidió que ninguno de esos valores cambiará nunca. Y si se refería a volátil para aplicar al valor y no al puntero, estaría jodido. De nuevo, es poco probable, pero es mejor equivocarse al hacer las cosas bien, que aprovechar el comportamiento que sucede hoy en día.
@curiousguy no decidió mal. Hizo la deducción correcta en función de la información proporcionada. Si no puede marcar algo volátil, el compilador puede asumir que no es volátil . Eso es lo que hace el compilador cuando optimiza el código. Si hay más información, es decir, que dichos datos son de hecho volátiles, es responsabilidad del programador proporcionar esa información. Lo que estás reclamando por un compilador con errores es realmente una mala programación.
iheanyi
1
@curiousguy no, solo porque la palabra clave volátil aparezca una vez no significa que todo se vuelva volátil de repente. Di un escenario en el que el compilador hace lo correcto y logra un resultado que es contrario a lo que el programador espera erróneamente. Al igual que el "análisis más irritante" no es el signo del error del compilador, tampoco es el caso aquí.
iheanyi
82
volatilees necesario cuando se desarrollan sistemas embebidos o controladores de dispositivos, donde necesita leer o escribir un dispositivo de hardware mapeado en memoria. El contenido de un registro de dispositivo particular puede cambiar en cualquier momento, por lo que necesita la volatilepalabra clave para asegurarse de que el compilador no optimice dichos accesos.
Esto no solo es válido para sistemas integrados, sino también para el desarrollo de todos los controladores de dispositivos.
Mladen Janković
La única vez que lo necesité en un bus ISA de 8 bits donde leyó la misma dirección dos veces: el compilador tuvo un error y lo ignoró (principios de Zortech c ++)
Martin Beckett
Muy poco volátil es adecuado para el control de dispositivos externos. Su semántica es incorrecta para el MMIO moderno: tiene que hacer que demasiados objetos sean volátiles y perjudica la optimización. Pero el MMIO moderno se comporta como la memoria normal hasta que se establece un indicador, por lo que no debería ser necesario volátil. Muchos conductores nunca usan volátiles.
curioso
69
Algunos procesadores tienen registros de coma flotante que tienen más de 64 bits de precisión (por ejemplo, 32 bits x86 sin SSE, vea el comentario de Peter). De esa forma, si ejecuta varias operaciones con números de doble precisión, 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 asignó registros e hizo optimizaciones, tendrá resultados diferentes para las mismas operaciones en las mismas entradas. Si necesita coherencia, puede forzar que cada operación regrese 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 coma 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.
Cuando calcula derivadas numéricas también es útil, para asegurarse de que x + h - x == h defina hh = x + h - x como volátil para que se pueda calcular un delta adecuado.
Alexandre C.
55
+1, de hecho, en mi experiencia, hubo un caso en que los cálculos de punto flotante produjeron resultados diferentes en Debug y Release, por lo que las pruebas unitarias escritas para una configuración fallaban para otra. Lo resolvimos mediante la declaración de una variable de punto flotante como en volatile doublelugar de solo double, para asegurarnos de que se trunca de la precisión de FPU a la precisión de 64 bits (RAM) antes de continuar con los cálculos adicionales. Los resultados fueron sustancialmente diferentes debido a una exageración adicional del error de coma flotante.
Serge Rogatch
Su definición de "moderno" está un poco fuera de lugar. Esto solo afecta al código x86 de 32 bits que evita SSE / SSE2, y no era "moderno" incluso hace 10 años. MIPS / ARM / POWER tienen registros de hardware de 64 bits, y también x86 con SSE2. Las implementaciones de C ++ x86-64 siempre usan SSE2, y los compiladores tienen opciones como g++ -mfpmath=sseusarlo también para x86 de 32 bits. Puede usar gcc -ffloat-storepara forzar el redondeo en todas partes incluso cuando usa x87, o puede configurar la precisión x87 en una mantisa de 53 bits: randomascii.wordpress.com/2012/03/21/… .
Peter Cordes
Pero sigue siendo una buena respuesta, para x87 code-gen obsoleto, puede usar volatilepara forzar el redondeo en algunos lugares específicos sin perder los beneficios en todas partes.
Peter Cordes
1
¿O confundo inexacto con inconsistente?
Chipster
49
De un artículo de "Volátil como promesa" 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 a pesar de que no parece haber ninguna declaración en el programa que lo cambie ".
Aquí hay enlaces a tres de sus artículos sobre la volatilepalabra clave:
DEBE usar volátiles al implementar 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, volátil le dice al compilador que los accesos a esta variable deben corresponder a una operación de lectura / escritura de memoria física.
Por ejemplo, así es como se declara InterlockedIncrement en la API Win32:
LONG __cdecl InterlockedIncrement(
__inout LONG volatile*Addend);
No es necesario que declare una variable volátil para poder utilizar InterlockedIncrement.
curioso
Esta respuesta es obsoleta ahora que proporciona C ++ 11 std::atomic<LONG>para que pueda escribir código sin bloqueo de manera más segura sin problemas de tener cargas puras / tiendas puras optimizadas, reordenadas o cualquier otra cosa.
Peter Cordes
10
Una aplicación grande en la que solía trabajar a principios de la década de 1990 contenía manejo de excepciones basado en C usando setjmp y longjmp. La palabra clave volátil era necesaria en las variables cuyos valores debían ser preservados en el bloque de código que servía como cláusula "catch", para que esos vars no se almacenaran en registros y fueran eliminados por el longjmp.
En el Estándar C, uno de los lugares para usar volatilees con un controlador 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 volatile sig_atomic_tvariable o salir rápidamente. De hecho, AFAIK, es el único lugar en el Estándar C en el que volatilese requiere el uso para evitar un comportamiento indefinido.
ISO / IEC 9899: 2011 §7.14.1.1 La signalfunción
¶5 Si la señal se produce de otra manera que como resultado de llamar a la función aborto raise, el comportamiento no está definido si el controlador de señal se refiere a cualquier objeto con una duración de almacenamiento estático o de subprocesos que no sea un objeto atómico sin bloqueo que no sea mediante la asignación de un valor a un objeto declarado como volatile sig_atomic_t, o el manejador de señal llama a cualquier función en la biblioteca estándar que no sea la abortfunción, la _Exitfunción, la
quick_exitfunción o la signalfunción con el primer argumento igual al número de señal correspondiente a la señal que causó la invocación del manipulador. Además, si dicha llamada a la signalfunción da como resultado un retorno SIG_ERR, el valor de errnoes indeterminado. 252)
252) Si un controlador de señal asíncrono genera alguna señal, el comportamiento es indefinido.
Eso significa que en el Estándar C, puedes escribir:
POSIX es mucho más indulgente con respecto a lo que puede hacer en un controlador de señal, 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 forma segura).
Desarrollando para un incrustado, tengo un bucle que verifica una variable que se puede cambiar en un controlador de interrupciones. Sin "volátil", el bucle se convierte en un noop, por lo que el compilador puede ver, la variable nunca cambia, por lo que optimiza la verificación.
Lo mismo se aplicaría a una variable que se puede cambiar 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.
Además de usarlo según lo previsto, se utiliza volátil en la metaprogramación (plantilla). Se puede utilizar para evitar la sobrecarga accidental, ya que el atributo volátil (como const) participa en la resolución de sobrecarga.
Esto es legal; ambas sobrecargas son potencialmente invocables y hacen casi lo mismo. El reparto en la volatilesobrecarga es legal ya que sabemos que la barra no pasará un no volátil de Ttodos modos. Sin volatileembargo, la versión es estrictamente peor, por lo que nunca se elige en resolución de sobrecarga si no es volátilf está disponible.
Tenga en cuenta que el código nunca depende del volatileacceso a la memoria.
¿Podría por favor explicar esto con un ejemplo? Realmente me ayudaría a entender mejor. ¡Gracias!
Batbrat
" El lanzamiento en la sobrecarga volátil " Un lanzamiento es una conversión explícita. Es una construcción SYNTAX. Mucha gente hace esa confusión (incluso los autores estándar).
curioso
6
debe usarlo para implementar spinlocks, así como algunas estructuras de datos sin bloqueo (¿todas?)
Úselo con operaciones atómicas / instrucciones
una vez me ayudó a superar el error del compilador (código generado incorrectamente durante la optimización)
Es mejor usar una biblioteca, intrínsecos del compilador o código de ensamblaje en línea. Volátil no es confiable.
Zan Lynx
1
1 y 2 hacen uso de operaciones atómicas, pero volátil no proporciona semántica atómica y las implementaciones de atómica específicas de la plataforma reemplazarán la necesidad de usar volátiles, por lo que para 1 y 2, no estoy de acuerdo, NO es necesario volátil para estos.
¿Quién dice algo acerca de proporcionar semántica atómica volátil? Dije que necesita USAR el volátil CON operaciones atómicas y si no cree que sea cierto, mire las declaraciones de operaciones entrelazadas de la API win32 (este tipo también explicó esto en su respuesta)
Mladen Janković
4
los volatile palabra clave 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 volatilese omiten de la optimización porque sus valores pueden ser cambiados por código fuera del alcance del código actual en cualquier momento. El sistema siempre lee el valor actual de un volatileobjeto desde la ubicación de la memoria en lugar de mantener su valor en el registro temporal en el punto en el que se solicita, incluso si una instrucción previa solicitaba 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 calificador volátil, pueden surgir los siguientes problemas
1) El código puede no funcionar como se esperaba cuando la optimización está activada.
2) El código puede no funcionar como se esperaba cuando las interrupciones están habilitadas y utilizadas.
El enlace que publicó está extremadamente desactualizado y no refleja las mejores prácticas actuales.
Tim Seguine
2
Aparte del hecho de que la palabra clave volátil se utiliza para decirle al compilador no para optimizar el acceso a alguna variable (que pueden ser modificados por un hilo o una rutina de interrupción), se puede también utilizar para eliminar algunos errores de compilación - Sí, se 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 correctamente. 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.
Es una suposición errónea la idea de que el compilador no optimiza el acceso a los volátiles. El estándar no sabe nada sobre optimizaciones. Se requiere que el compilador respete lo que dicta el estándar, pero es libre de hacer cualquier optimización que no interfiera con el comportamiento normal.
Terminus
3
Desde mi experiencia, el 99.9% de todos los "errores" de optimización en gcc arm son errores por parte del programador. No tengo idea si esto se aplica a esta respuesta. Solo una queja sobre el tema general
odinthenerd
@Terminus " Es una suposición errónea la idea de que el compilador no optimiza el acceso a los volátiles " ¿Fuente?
curioso
2
¿Su programa parece funcionar incluso sin volatilepalabra clave? Quizás esta sea la razón:
Como se mencionó anteriormente, la volatilepalabra clave ayuda para casos como
volatileint* p =...;// point to some memorywhile(*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 volatilecasi el mismo resultado se genera.
Mientras g () pueda estar completamente en línea, el compilador puede ver todo lo que está sucediendo y, por lo tanto, puede optimizarlo. Pero cuando el programa hace 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 () esté en línea (ya sea debido a cambios explícitos o debido a la inteligencia del compilador / enlazador), ¡entonces su código podría romperse si olvida la volatilepalabra clave!
Por lo tanto, recomiendo agregar la volatilepalabra clave incluso si su programa parece funcionar sin él. Hace que la intención sea más clara y robusta con respecto a los cambios futuros.
Tenga en cuenta que una función puede tener su código en línea mientras genera una referencia (resuelta en el momento del enlace) a la función de esquema; Este será el caso de una función recursiva parcialmente en línea. Una función también podría tener su semántica "en línea" por el compilador, es decir, el compilador asume que los efectos secundarios y el resultado están dentro de los posibles efectos secundarios y resultados posibles de acuerdo con su código fuente, mientras que aún no lo incluye. Esto se basa en la "Regla efectiva de una definición" que establece que todas las definiciones de una entidad serán efectivamente equivalentes (si no exactamente idénticas).
curioso
Es posible evitar de forma portátil la alineación de una llamada (o "alineación" de su semántica) por una función cuyo cuerpo es visible por el compilador (incluso en el momento del enlace con la optimización global) es posible utilizando un volatilepuntero de función calificado:void (* volatile fun_ptr)() = fun; fun_ptr();
curiousguy
2
En los primeros días de C, los compiladores interpretarían todas las acciones que leen y escriben valores como operaciones de memoria, para realizarse en la misma secuencia que las lecturas y escrituras aparecieron en el código. La eficiencia podría mejorarse enormemente en muchos casos si los compiladores tuvieran cierta libertad para reordenar y consolidar operaciones, pero hubo un problema con esto. Incluso las operaciones a menudo se especificaban en un cierto orden simplemente porque era necesario especificarlas en algún orden y, por lo tanto, el programador eligió una de muchas alternativas igualmente buenas, que no siempre fue el caso. Algunas veces sería importante que ciertas operaciones ocurran en una secuencia particular.
Exactamente qué detalles de la secuencia son importantes variará 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 mejor le parezca. Si una acción se realiza con un valor volatilel calificado, una implementación de calidad debe ofrecer las garantías de pedido adicionales que pueda requerir el código dirigido a su plataforma y campo de aplicación previstos, 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 han optado por ofrecer las mínimas garantías exigidas por el Estándar. Esto hace volatilemucho 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 volverá a hacerlo hasta que la otra tarea lo haya hecho] debe hacer uno de cuatro cosas:
Ponga la adquisición y el lanzamiento del mutex en una función que el compilador no puede en línea y a la que no puede aplicar la optimización de todo el programa.
Califique todos los objetos guardados por el mutex como volatilealgo que no debería ser necesario si todos los accesos ocurren después de adquirir el mutex y antes de liberarlo.
Use el nivel de optimización 0 para forzar al compilador a generar código como si todos los objetos que no registerestán calificados lo estén volatile.
Use las directivas específicas de gcc.
Por el contrario, cuando se utiliza un compilador de mayor calidad que es más adecuado para la programación de sistemas, como icc, uno tendría otra opción:
Asegúrese de que se volatilerealice una escritura calificada en todos los lugares donde se necesite una adquisición o liberación.
La adquisición de un "mutex de traspaso básico" requiere una volatilelectura (para ver si está lista), y no debería requerir una volatileescritura también (el otro lado no intentará volver a adquirirlo hasta que se devuelva) pero tiene que Realizar una volatileescritura sin sentido es aún mejor que cualquiera de las opciones disponibles bajo gcc o clang.
Un uso que debo recordarle es que, en la función de controlador de señal, si desea acceder / modificar una variable global (por ejemplo, marcarla como exit = true), debe declarar esa variable como 'volátil'.
Todas las respuestas son excelentes. Pero además de eso, me gustaría compartir un ejemplo.
A continuación se muestra un pequeño programa cpp:
#include<iostream>int x;int main(){char buf[50];
x =8;if(x ==8)
printf("x is 8\n");else
sprintf(buf,"x is not 8\n");
x=1000;while(x >5)
x--;return0;}
Ahora, generemos el ensamblaje del código anterior (y pegaré solo las partes del ensamblaje que sean relevantes aquí):
El comando para generar ensamblaje:
g++-S -O3 -c -fverbose-asm-Wa,-adhln assembly.cpp
Y la asamblea:
main:.LFB1594:
subq $40,%rsp #,.seh_stackalloc 40.seh_endprologue
# assembly.cpp:5: int main(){
call __main ## assembly.cpp:10: printf("x is 8\n");
leaq .LC0(%rip),%rcx #,# assembly.cpp:7: x = 8;
movl $8, x(%rip)#, x# assembly.cpp:10: printf("x is 8\n");
call _ZL6printfPKcz.constprop.0## assembly.cpp:18: }
xorl %eax,%eax #
movl $5, x(%rip)#, x
addq $40,%rsp #,
ret
.seh_endproc
.p2align 4,,15.def _GLOBAL__sub_I_x;.scl 3;.type 32;.endef
.seh_proc _GLOBAL__sub_I_x
Puede ver en el ensamblado que el código de ensamblado no se generó sprintfporque el compilador asumió que xeso no cambiará fuera del programa. Y lo mismo ocurre con el whilebucle.whileEl bucle se eliminó por completo debido a la optimización porque el compilador lo vio como un código inútil y, por lo tanto, lo asignó directamente 5a x(ver movl $5, x(%rip)).
El problema ocurre cuando ¿qué pasa si un proceso / hardware externo cambiaría el valor de xalgún punto intermedio?x = 8; y if(x == 8). Es de esperar que el elsebloque funcione, pero desafortunadamente el compilador ha recortado esa parte.
Ahora, para resolver esto, en el assembly.cpp, cambiemos int x;avolatile int x; y rápidamente ver el código ensamblador generado:
Aquí se puede ver que los códigos de montaje para sprintf, printfy whilese generaron bucle. La ventaja es que si xalgún programa externo o hardware cambia la variable sprintf, se ejecutará parte del código. Y de forma similar, el whilebucle se puede usar para ocupados esperando ahora.
Otras respuestas ya mencionan evitar algunas optimizaciones para:
usar registros mapeados en memoria (o "MMIO")
escribir controladores de dispositivo
permitir una depuración más fácil de programas
hacer cálculos de coma flotante más deterministas
La volátil es esencial siempre que necesite que un valor parezca provenir del exterior y sea impredecible y evite las optimizaciones del compilador en función de un valor conocido, y cuando un resultado no se usa realmente pero necesita que se calcule, o se usa, pero desea calcularlo varias veces para un punto de referencia, y necesita que los cálculos comiencen y terminen en puntos precisos.
Una lectura volátil es como una operación de entrada (como scanfo un uso de cin): el valor parece provenir del exterior del programa, por lo que cualquier cálculo que dependa del valor debe comenzar después .
Una escritura volátil es como una operación de salida (como printfo un uso de cout): el valor parece comunicarse fuera del programa, por lo que si el valor depende de un cálculo, debe terminarse antes .
Por lo tanto, se puede usar un par de lectura / escritura volátil para domar los puntos de referencia y hacer que la medición del tiempo sea significativa .
Sin volátil, el compilador podría iniciar su cálculo antes, ya que nada impediría la reordenación de los cálculos con funciones como la medición del tiempo .
volatile
se puede usar de manera efectiva, reunido en términos bastante simples. Enlace: Publicaciones.gbdirect.co.uk/c_book/chapter8/…volatile
más útil que lafriend
palabra clave.Respuestas:
volatile
es necesario si está leyendo desde un punto en la memoria que, por ejemplo, un proceso / dispositivo / lo que sea completamente separado en lo que pueda escribir.Solía trabajar con ram de doble puerto en un sistema multiprocesador en línea C. Usamos un valor de 16 bits administrado por hardware como semáforo para saber cuándo había terminado el otro tipo. Esencialmente hicimos esto:
Sin
volatile
, el optimizador ve el ciclo como inútil (¡El tipo nunca establece el valor! ¡Está loco, deshazte de ese código!) Y mi código continuaría sin haber adquirido el semáforo, causando problemas más adelante.fuente
uint16_t* volatile semPtr
se escribiera en su lugar? Esto debería marcar el puntero como volátil (en lugar del valor señalado), de modo que las comprobaciones en el puntero en sí, por ejemplosemPtr == SOME_ADDR
, no puedan optimizarse. Sin embargo, esto también implica un valor puntiagudo volátil nuevamente. ¿No?volatile
es necesario cuando se desarrollan sistemas embebidos o controladores de dispositivos, donde necesita leer o escribir un dispositivo de hardware mapeado en memoria. El contenido de un registro de dispositivo particular puede cambiar en cualquier momento, por lo que necesita lavolatile
palabra clave para asegurarse de que el compilador no optimice dichos accesos.fuente
Algunos procesadores tienen registros de coma flotante que tienen más de 64 bits de precisión (por ejemplo, 32 bits x86 sin SSE, vea el comentario de Peter). De esa forma, si ejecuta varias operaciones con números de doble precisión, 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 asignó registros e hizo optimizaciones, tendrá resultados diferentes para las mismas operaciones en las mismas entradas. Si necesita coherencia, puede forzar que cada operación regrese 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 coma 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.
fuente
volatile double
lugar de solodouble
, para asegurarnos de que se trunca de la precisión de FPU a la precisión de 64 bits (RAM) antes de continuar con los cálculos adicionales. Los resultados fueron sustancialmente diferentes debido a una exageración adicional del error de coma flotante.g++ -mfpmath=sse
usarlo también para x86 de 32 bits. Puede usargcc -ffloat-store
para forzar el redondeo en todas partes incluso cuando usa x87, o puede configurar la precisión x87 en una mantisa de 53 bits: randomascii.wordpress.com/2012/03/21/… .volatile
para forzar el redondeo en algunos lugares específicos sin perder los beneficios en todas partes.De un artículo de "Volátil como promesa" de Dan Saks:
Aquí hay enlaces a tres de sus artículos sobre la
volatile
palabra clave:fuente
DEBE usar volátiles al implementar 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, volátil le dice al compilador que los accesos a esta variable deben corresponder a una operación de lectura / escritura de memoria física.
Por ejemplo, así es como se declara InterlockedIncrement en la API Win32:
fuente
std::atomic<LONG>
para que pueda escribir código sin bloqueo de manera más segura sin problemas de tener cargas puras / tiendas puras optimizadas, reordenadas o cualquier otra cosa.Una aplicación grande en la que solía trabajar a principios de la década de 1990 contenía manejo de excepciones basado en C usando setjmp y longjmp. La palabra clave volátil era necesaria en las variables cuyos valores debían ser preservados en el bloque de código que servía como cláusula "catch", para que esos vars no se almacenaran en registros y fueran eliminados por el longjmp.
fuente
En el Estándar C, uno de los lugares para usar
volatile
es con un controlador 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 unavolatile sig_atomic_t
variable o salir rápidamente. De hecho, AFAIK, es el único lugar en el Estándar C en el quevolatile
se requiere el uso para evitar un comportamiento indefinido.Eso significa que en el Estándar C, puedes escribir:
Y no mucho más.
POSIX es mucho más indulgente con respecto a lo que puede hacer en un controlador de señal, 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 forma segura).fuente
Desarrollando para un incrustado, tengo un bucle que verifica una variable que se puede cambiar en un controlador de interrupciones. Sin "volátil", el bucle se convierte en un noop, por lo que el compilador puede ver, la variable nunca cambia, por lo que optimiza la verificación.
Lo mismo se aplicaría a una variable que se puede cambiar 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.
fuente
Lo he usado en compilaciones de depuración cuando el compilador insiste en optimizar una variable que quiero ver mientras paso por el código.
fuente
Además de usarlo según lo previsto, se utiliza volátil en la metaprogramación (plantilla). Se puede utilizar para evitar la sobrecarga accidental, ya que el atributo volátil (como const) participa en la resolución de sobrecarga.
Esto es legal; ambas sobrecargas son potencialmente invocables y hacen casi lo mismo. El reparto en la
volatile
sobrecarga es legal ya que sabemos que la barra no pasará un no volátil deT
todos modos. Sinvolatile
embargo, la versión es estrictamente peor, por lo que nunca se elige en resolución de sobrecarga si no es volátilf
está disponible.Tenga en cuenta que el código nunca depende del
volatile
acceso a la memoria.fuente
fuente
los
volatile
palabra clave 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 pueden ser cambiados por código fuera del alcance del código actual en cualquier momento. El sistema siempre lee el valor actual de unvolatile
objeto desde la ubicación de la memoria en lugar de mantener su valor en el registro temporal en el punto en el que se solicita, incluso si una instrucción previa solicitaba 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 calificador volátil, pueden surgir los siguientes problemas
1) El código puede no funcionar como se esperaba cuando la optimización está activada.
2) El código puede no funcionar como se esperaba cuando las interrupciones están habilitadas y utilizadas.
Volátil: el mejor amigo de un programador
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
fuente
Aparte del hecho de que la palabra clave volátil se utiliza para decirle al compilador no para optimizar el acceso a alguna variable (que pueden ser modificados por un hilo o una rutina de interrupción), se puede también utilizar para eliminar algunos errores de compilación - Sí, se 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 correctamente. 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.
fuente
¿Su programa parece funcionar incluso sin
volatile
palabra clave? Quizás esta sea la razón:Como se mencionó anteriormente, la
volatile
palabra clave ayuda para casos comoPero parece que casi no hay efecto una vez que se llama a una función externa o no en línea. P.ej:
Luego con o sin
volatile
casi el mismo resultado se genera.Mientras g () pueda estar completamente en línea, el compilador puede ver todo lo que está sucediendo y, por lo tanto, puede optimizarlo. Pero cuando el programa hace 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 () esté en línea (ya sea debido a cambios explícitos o debido a la inteligencia del compilador / enlazador), ¡entonces su código podría romperse si olvida la
volatile
palabra clave!Por lo tanto, recomiendo agregar la
volatile
palabra clave incluso si su programa parece funcionar sin él. Hace que la intención sea más clara y robusta con respecto a los cambios futuros.fuente
volatile
puntero de función calificado:void (* volatile fun_ptr)() = fun; fun_ptr();
En los primeros días de C, los compiladores interpretarían todas las acciones que leen y escriben valores como operaciones de memoria, para realizarse en la misma secuencia que las lecturas y escrituras aparecieron en el código. La eficiencia podría mejorarse enormemente en muchos casos si los compiladores tuvieran cierta libertad para reordenar y consolidar operaciones, pero hubo un problema con esto. Incluso las operaciones a menudo se especificaban en un cierto orden simplemente porque era necesario especificarlas en algún orden y, por lo tanto, el programador eligió una de muchas alternativas igualmente buenas, que no siempre fue el caso. Algunas veces sería importante que ciertas operaciones ocurran en una secuencia particular.
Exactamente qué detalles de la secuencia son importantes variará 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 mejor le parezca. Si una acción se realiza con un valorvolatile
l calificado, una implementación de calidad debe ofrecer las garantías de pedido adicionales que pueda requerir el código dirigido a su plataforma y campo de aplicación previstos, 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 han optado por ofrecer las mínimas garantías exigidas por el Estándar. Esto hace
volatile
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 volverá a hacerlo hasta que la otra tarea lo haya hecho] debe hacer uno de cuatro cosas:Ponga la adquisición y el lanzamiento del mutex en una función que el compilador no puede en línea y a la que no puede aplicar la optimización de todo el programa.
Califique todos los objetos guardados 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.Use el nivel de optimización 0 para forzar al compilador a generar código como si todos los objetos que no
register
están calificados lo esténvolatile
.Use las directivas específicas de gcc.
Por el contrario, cuando se utiliza un compilador de mayor calidad que es más adecuado para la programación de sistemas, como icc, uno tendría otra opción:
volatile
realice una escritura calificada en todos los lugares 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 unavolatile
escritura también (el otro lado no intentará volver a adquirirlo hasta que se devuelva) pero tiene que Realizar unavolatile
escritura sin sentido es aún mejor que cualquiera de las opciones disponibles bajo gcc o clang.fuente
Un uso que debo recordarle es que, en la función de controlador de señal, si desea acceder / modificar una variable global (por ejemplo, marcarla como exit = true), debe declarar esa variable como 'volátil'.
fuente
Todas las respuestas son excelentes. Pero además de eso, me gustaría compartir un ejemplo.
A continuación se muestra un pequeño programa cpp:
Ahora, generemos el ensamblaje del código anterior (y pegaré solo las partes del ensamblaje que sean relevantes aquí):
El comando para generar ensamblaje:
Y la asamblea:
Puede ver en el ensamblado que el código de ensamblado no se generó
sprintf
porque el compilador asumió quex
eso no cambiará fuera del programa. Y lo mismo ocurre con elwhile
bucle.while
El bucle se eliminó por completo debido a la optimización porque el compilador lo vio como un código inútil y, por lo tanto, lo asignó directamente5
ax
(vermovl $5, x(%rip)
).El problema ocurre cuando ¿qué pasa si un proceso / hardware externo cambiaría el valor de
x
algún punto intermedio?x = 8;
yif(x == 8)
. Es de esperar que elelse
bloque funcione, pero desafortunadamente el compilador ha recortado esa parte.Ahora, para resolver esto, en el
assembly.cpp
, cambiemosint x;
avolatile int x;
y rápidamente ver el código ensamblador generado:Aquí se puede ver que los códigos de montaje para
sprintf
,printf
ywhile
se generaron bucle. La ventaja es que six
algún programa externo o hardware cambia la variablesprintf
, se ejecutará parte del código. Y de forma similar, elwhile
bucle se puede usar para ocupados esperando ahora.fuente
Otras respuestas ya mencionan evitar algunas optimizaciones para:
La volátil es esencial siempre que necesite que un valor parezca provenir del exterior y sea impredecible y evite las optimizaciones del compilador en función de un valor conocido, y cuando un resultado no se usa realmente pero necesita que se calcule, o se usa, pero desea calcularlo varias veces para un punto de referencia, y necesita que los cálculos comiencen y terminen en puntos precisos.
Una lectura volátil es como una operación de entrada (como
scanf
o un uso decin
): el valor parece provenir del exterior del programa, por lo que cualquier cálculo que dependa del valor debe comenzar después .Una escritura volátil es como una operación de salida (como
printf
o un uso decout
): el valor parece comunicarse fuera del programa, por lo que si el valor depende de un cálculo, debe terminarse antes .Por lo tanto, se puede usar un par de lectura / escritura volátil para domar los puntos de referencia y hacer que la medición del tiempo sea significativa .
Sin volátil, el compilador podría iniciar su cálculo antes, ya que nada impediría la reordenación de los cálculos con funciones como la medición del tiempo .
fuente