Volátil (programación informática)

format_list_bulleted Contenido keyboard_arrow_down
ImprimirCitar

En programación informática, volatile 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 volatile 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 mapeadas 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 se malinterpreta fácilmente. En C y C++, es un calificador de tipo, como const, y es una propiedad del type. 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, existe una palabra clave independiente shared para el uso de subprocesos, pero no existe ninguna palabra clave volatile.

En C y C++

En C, y en consecuencia en C++, la palabra clave volatile tenía como objetivo:

  • permitir el acceso a dispositivos I/O con memoria
  • permitir usos de variables entre setjmp y longjmp
  • permitir usos de sig_atomic_t variables en controladores de señal.

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, p. mediante el uso de registros para el almacenamiento de valores intermedios.

Aunque está previsto tanto en C como en C++, los estándares de C no expresan que la semántica volatile se refiere al valor l, no al objeto referenciado. El respectivo informe de defectos DR 476 (a C11) aún está bajo revisión con C17.

Operaciones sobre volatile variables no son atómicas, ni establecen una relación apropiada-antes para el roscado. Esto se especifica en las normas pertinentes (C, C++, POSIX, WIN32), y las variables volátiles no son inseguras en la gran mayoría de las implementaciones actuales. Así, el uso de volatile palabra clave como mecanismo de sincronización portátil es desalentado por muchos grupos C/C++.

Ejemplo de E/S asignadas en memoria en C

En este ejemplo, el código establece el valor almacenado en foo en 0. Luego comienza a sondear ese valor repetidamente hasta que cambia a 255:

estática int Foo;vacío bar()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 a 0 en todo momento. Por lo tanto, el compilador reemplazará el cuerpo de la función con un bucle infinito similar a este:

vacío bar_optimized()vacío) {} 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 palabra clave volatile, 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, se utiliza la palabra clave volatile:

estática volátil int Foo;vacío bar ()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 subprocesos múltiples. escenarios; 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.

Comparación de optimización en C

Los siguientes programas en C y los extractos en lenguaje ensamblador que los acompañan demuestran cómo la palabra clave volatile 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 objetos volatile es más detallado, lo que lo hace más largo para que se pueda cumplir la naturaleza de los objetos volatile. . La palabra clave volatile 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 palabra clave volatile, 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.

C++11

De acuerdo con 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 plantillas std::atomic<T>.

En Java

El lenguaje de programación Java también tiene la palabra clave volatile, pero se utiliza para un propósito algo diferente. Cuando se aplica a un campo, el calificador Java volatile proporciona las siguientes garantías:

  • En todas las versiones de Java, hay un orden global sobre lecturas y escritos de todas las variables volátiles (este orden global sobre volatiles es un orden parcial sobre las mayores orden de sincronización (que es un orden total sobre todo acciones de sincronización)). Esto implica que cada hilo que accede a un campo volátil leerá su valor actual antes de continuar, en lugar de (potencialmente) utilizando un valor caché. (Sin embargo, no hay garantía sobre el ordenamiento relativo de lecturas volátiles y escribe con lecturas y escritos regulares, lo que significa que generalmente no es una construcción de rosca útil.)
  • En Java 5 o posterior, las lecturas y escritos volátiles establecen una relación anterior a la aparición, como adquirir y liberar un mutex.

Usar volatile puede ser más rápido que un bloqueo, pero no funcionará en algunas situaciones antes de Java 5. La variedad de situaciones en las que volatile es efectivo se amplió en Java 5; en particular, el bloqueo de doble verificación ahora funciona correctamente.

En C#

En C#, volatile garantiza que el código que accede al campo no esté sujeto a algunas optimizaciones inseguras para subprocesos que puedan ser realizadas por el compilador, el CLR o el hardware. Cuando un campo está marcado como volátil, se le indica al compilador que genere una "barrera de memoria" o "cerca" a su alrededor, lo que evita el reordenamiento de instrucciones o el almacenamiento en caché vinculados al campo. Al leer un campo volatile, el compilador genera una acquire-fence, que evita que otras lecturas y escrituras en el campo se muevan antes de la valla. . Al escribir en un campo volatile, el compilador genera un release-fence; esta barrera evita que otras lecturas y escrituras en el campo se muevan después de la barrera.

Solo los siguientes tipos pueden marcarse como volatile: todos los tipos de referencia, Single, Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Char< /code> y todos los tipos enumerados con un tipo subyacente de Byte, SByte, Int16, UInt16, < código>Int32 o UInt32. (Esto excluye las estructuras de valor, así como los tipos primitivos Double, Int64, UInt64 y Decimal.)

El uso de la palabra clave volatile no admite campos que se pasan por referencia o variables locales capturadas; en estos casos, se deben utilizar Thread.VolatileRead y Thread.VolatileWrite.

En efecto, estos métodos desactivan algunas optimizaciones que normalmente realizan 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 palabra clave volatile: en lugar de generar un &# 34;media valla" (es decir, una barrera de adquisición solo evita el reordenamiento de las instrucciones y el almacenamiento en caché que le preceden), VolatileRead y VolatileWrite generan una barrera "completa" que impiden el reordenamiento de instrucciones y el almacenamiento en caché de ese campo en ambas direcciones. Estos métodos funcionan de la siguiente manera:

  • El Thread.VolatileWrite método obliga al valor en el campo a ser escrito en el punto de la llamada. Además, cualquier cargas y tiendas anteriores del programa-orden debe ocurrir antes de la llamada a VolatileWrite y cualquier cargas y tiendas posteriores del programa-orden debe ocurrir después de la llamada.
  • El Thread.VolatileRead método obliga al valor en el campo a ser leído desde el punto de la llamada. Además, cualquier cargas y tiendas anteriores del programa-orden debe ocurrir antes de la llamada a VolatileRead y cualquier cargas y tiendas posteriores del programa-orden debe ocurrir después de la llamada.

El Thread.VolatileRead y Thread.VolatileWrite métodos generan una valla completa llamando a la Thread.MemoryBarrier método, que construye una barrera de memoria que funciona en ambas direcciones. Además de las motivaciones para usar una valla completa dada arriba, un problema potencial con la volatile palabra clave que se resuelve utilizando una valla completa generada por Thread.MemoryBarrier es el siguiente: debido a la naturaleza asimétrica de medias cercas, a volatile campo con una instrucción de escritura seguida de una instrucción de lectura puede todavía tener la orden de ejecución intercambiada por el compilador. Debido a que las cercas completas son simétricas, esto no es un problema al usar Thread.MemoryBarrier.

En Fortran

VOLATILE es parte del estándar Fortran 2003, aunque la versión anterior lo admitía como una extensión. Hacer que todas las variables sean volatile en una función también es ú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énticasescribir()*,*) i#2 ! Carga la variable una vez desde la memoria y multiplica que los tiempos de valor en síescribir()*,*) i*i ! Carga la variable dos veces de memoria y multiplica esos valores

Al "reducir" siempre a la memoria de un VOLATILE, el compilador de Fortran se excluye de reordenar lecturas o escribe a volatiles. Esto hace visible a otros hilos acciones hechas en este hilo, y viceversa.

El uso de VOLATILE reduce e incluso puede impedir la optimización.

Más resultados...
Tamaño del texto:
undoredo
format_boldformat_italicformat_underlinedstrikethrough_ssuperscriptsubscriptlink
save