En programación de computadoras , volátil significa que un valor es propenso a cambiar con el tiempo, fuera del control de algún código. La volatilidad tiene implicaciones dentro de las convenciones de llamada de funciones y también afecta la forma en que se almacenan, acceden y almacenan en caché las variables.
En los lenguajes de programación C , C++ , C# y Java , la palabra clave volátil indica que un valor puede cambiar entre diferentes accesos, incluso si no parece estar modificado. Esta palabra clave evita que un compilador de optimización optimice lecturas o escrituras posteriores y, por lo tanto, reutilice incorrectamente un valor obsoleto u omita escrituras. Los valores volátiles surgen principalmente en el acceso al hardware ( E/S asignadas en memoria ), donde se utiliza la lectura o escritura en la memoria para comunicarse con dispositivos periféricos , y en el subproceso , donde un subproceso diferente puede haber modificado un valor.
A pesar de ser una palabra clave común, el comportamiento de volatile
difiere significativamente entre lenguajes de programación y es fácil de malinterpretar. En C y C++, es un calificador de tipo , como const
, y es una propiedad del tipo . Además, en C y C++ no funciona en la mayoría de los escenarios de subprocesamiento y se desaconseja su uso. En Java y C#, es una propiedad de una variable e indica que el objeto al que está vinculada la variable puede mutar y está diseñado específicamente para subprocesos. En el lenguaje de programación D , hay una palabra clave separada shared
para el uso de subprocesos, pero no volatile
existe ninguna palabra clave.
En C, y en consecuencia en C++, la volatile
palabra clave tenía como objetivo: [1]
setjmp
ylongjmp
sig_atomic_t
variables en manejadores de señales.Dado que las variables marcadas como volátiles son propensas a cambiar fuera del flujo de código estándar, el compilador debe realizar cada lectura y escritura en la variable como lo indica el código. Cualquier acceso a variables volátiles no se puede optimizar, por ejemplo mediante el uso de registros para el almacenamiento de valores intermedios.
Si bien está previsto tanto en C como en C++, los estándares de C no expresan que la volatile
semántica se refiere al valor l, no al objeto al que se hace referencia. El respectivo informe de defectos DR 476 (a C11) aún está bajo revisión con C17 . [2]
Las operaciones sobre volatile
variables no son atómicas , ni establecen una relación adecuada de ocurre antes para el subprocesamiento. Esto se especifica en los estándares relevantes (C, C++, POSIX , WIN32), [1] y las variables volátiles no son seguras para subprocesos en la gran mayoría de las implementaciones actuales. Por lo tanto, volatile
muchos grupos de C/C++ desaconsejan el uso de palabras clave como mecanismo de sincronización portátil. [3] [4] [5]
En este ejemplo, el código establece el valor almacenado foo
en 0
. Luego comienza a sondear ese valor repetidamente hasta que cambia a 255
:
estático int foo ; barra vacía ( vacío ) { foo = 0 ; mientras ( foo ! = 255 ) ; }
Un compilador optimizador notará que ningún otro código puede cambiar el valor almacenado en foo
y asumirá que permanecerá igual en 0
todo momento. Por lo tanto, el compilador reemplazará el cuerpo de la función con un bucle infinito similar a este:
void bar_optimized ( void ) { foo = 0 ; mientras ( verdadero ) ; }
Sin embargo, foo
puede representar una ubicación que otros elementos del sistema informático pueden cambiar en cualquier momento, como un registro de hardware de un dispositivo conectado a la CPU . El código anterior nunca detectaría tal cambio; sin la volatile
palabra clave, el compilador asume que el programa actual es la única parte del sistema que podría cambiar el valor (que es, con diferencia, la situación más común).
Para evitar que el compilador optimice el código como se indica arriba, volatile
se utiliza la palabra clave:
estático volátil int foo ; barra vacía ( vacío ) { foo = 0 ; mientras ( foo ! = 255 ) ; }
Con esta modificación, la condición del bucle no se optimizará y el sistema detectará el cambio cuando ocurra.
Generalmente, hay operaciones de barrera de memoria disponibles en plataformas (que están expuestas en C++ 11) que deben preferirse en lugar de volátiles, ya que permiten que el compilador realice una mejor optimización y, lo que es más importante, garantizan un comportamiento correcto en escenarios de subprocesos múltiples; ni la especificación C (anterior a C11) ni la especificación C++ (anterior a C++11) especifican un modelo de memoria multiproceso, por lo que es posible que volátil no se comporte de manera determinista entre sistemas operativos, compiladores y CPU. [6]
Los siguientes programas en C y los extractos en lenguaje ensamblador que los acompañan demuestran cómo la volatile
palabra clave afecta la salida del compilador. El compilador en este caso fue GCC .
Al observar el código ensamblador, es claramente visible que el código generado con volatile
objetos es más detallado, haciéndolo más largo para que volatile
se pueda cumplir con la naturaleza de los objetos. La volatile
palabra clave evita que el compilador realice optimización en el código que involucra objetos volátiles, asegurando así que cada asignación y lectura de variable volátil tenga un acceso a memoria correspondiente. Sin la volatile
palabra clave, el compilador sabe que no es necesario volver a leer una variable desde la memoria en cada uso, porque no debería haber ninguna escritura en su ubicación de memoria desde ningún otro subproceso o proceso.
Según el estándar ISO C++11 , la palabra clave volátil solo debe usarse para acceso al hardware; no lo utilice para la comunicación entre subprocesos. Para la comunicación entre subprocesos, la biblioteca estándar proporciona std::atomic<T>
plantillas. [7]
El lenguaje de programación Java también tiene la volatile
palabra clave, pero se utiliza para un propósito algo diferente. Cuando se aplica a un campo, el calificador de Java volatile
proporciona las siguientes garantías:
El uso volatile
puede ser más rápido que un bloqueo , pero no funcionará en algunas situaciones antes de Java 5. [10] La gama de situaciones en las que volátil es efectivo se amplió en Java 5; en particular, el bloqueo de doble verificación ahora funciona correctamente. [11]
En C# , volatile
garantiza que el código que accede al campo no esté sujeto a algunas optimizaciones no seguras para subprocesos que puedan realizar el compilador, el CLR o el hardware. Cuando un campo está marcado volatile
, se le indica al compilador que genere una "barrera de memoria" o "valla" a su alrededor, lo que evita el reordenamiento de las instrucciones o el almacenamiento en caché vinculados al campo. Al leer un volatile
campo, el compilador genera una valla de adquisición , que evita que otras lecturas y escrituras en el campo se muevan antes de la valla. Al escribir en un volatile
campo, el compilador genera una valla de liberación ; esta barrera evita que otras lecturas y escrituras en el campo se muevan después de la barrera. [12]
Solo se pueden marcar los siguientes tipos volatile
: todos los tipos de referencia, Single
, Boolean
, Byte
, SByte
, Int16
, UInt16
, Int32
, UInt32
, Char
y todos los tipos enumerados con un tipo subyacente de Byte
, SByte
, Int16
, UInt16
, Int32
o UInt32
. [13] (Esto excluye las estructuras de valor , así como los tipos primitivos Double
, Int64
y UInt64
) Decimal
.
El uso de la volatile
palabra clave no admite campos que se pasan por referencia o variables locales capturadas ; en estos casos, Thread.VolatileRead
y Thread.VolatileWrite
debe usarse en su lugar. [12]
De hecho, estos métodos deshabilitan algunas optimizaciones que normalmente realiza el compilador de C#, el compilador JIT o la propia CPU. Las garantías proporcionadas por Thread.VolatileRead
y Thread.VolatileWrite
son un superconjunto de las garantías proporcionadas por la volatile
palabra clave: en lugar de generar una "media valla" (es decir, una valla de adquisición sólo evita el reordenamiento de instrucciones y el almacenamiento en caché que le preceden), VolatileRead
y VolatileWrite
generar una "valla completa" que evitar el reordenamiento de instrucciones y el almacenamiento en caché de ese campo en ambas direcciones. [12] Estos métodos funcionan de la siguiente manera: [14]
Thread.VolatileWrite
método obliga a escribir el valor del campo en el momento de la llamada. Además, cualquier carga y almacenamiento de orden de programa anterior debe ocurrir antes de la llamada VolatileWrite
y cualquier carga y almacenamiento de orden de programa posterior debe ocurrir después de la llamada.Thread.VolatileRead
método obliga a leer el valor del campo en el momento de la llamada. Además, cualquier carga y almacenamiento de orden de programa anterior debe ocurrir antes de la llamada VolatileRead
y cualquier carga y almacenamiento de orden de programa posterior debe ocurrir después de la llamada.Los métodos Thread.VolatileRead
y Thread.VolatileWrite
generan una barrera completa al llamar al Thread.MemoryBarrier
método, que construye una barrera de memoria que funciona en ambas direcciones. Además de las motivaciones para usar una barrera completa dadas anteriormente, un problema potencial con la volatile
palabra clave que se resuelve usando una barrera completa generada por Thread.MemoryBarrier
es el siguiente: debido a la naturaleza asimétrica de las medias barreras, un volatile
campo con una instrucción de escritura seguida de Es posible que el compilador aún cambie el orden de ejecución de una instrucción de lectura. Debido a que las vallas completas son simétricas, esto no es un problema cuando se usan Thread.MemoryBarrier
. [12]
VOLATILE
es parte del estándar Fortran 2003 , [15] aunque la versión anterior lo admitía como una extensión. Hacer que todas las variables volatile
en una función también sea útil para encontrar errores relacionados con el alias .
entero , volátil :: i ! Cuando no se define volátil, las siguientes dos líneas de código son idénticas, escriba ( * , * ) i ** 2 ! Carga la variable i una vez desde la memoria y multiplica ese valor por sí mismo escribe ( * , * ) i * i ! Carga la variable i dos veces desde la memoria y multiplica esos valores
Al "profundizar" siempre en la memoria de un VOLÁTIL, el compilador de Fortran no puede reordenar lecturas o escrituras en volátiles. Esto hace visibles para otros hilos las acciones realizadas en este hilo, y viceversa. [dieciséis]
El uso de VOLATILE reduce e incluso puede impedir la optimización. [17]
{{cite web}}
: Mantenimiento CS1: bot: estado de la URL original desconocido ( enlace )