volatile (programación de computadoras) - volatile (computer programming)
En la programación informática , en particular en el C , C ++ , C # , y Java lenguajes de programación , la volátil palabra clave indica que un valor puede cambiar entre los diferentes accesos, aunque no parece que ser modificado. Esta palabra clave evita que un compilador optimizador optimice las 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 mapeada en memoria ), donde la lectura o escritura en la memoria se usa 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 los 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 tipo . Además, en C y C ++ no funciona en la mayoría de los escenarios de subprocesos, 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á específicamente diseñado 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 C ++
En C, y en consecuencia C ++, la volatile
palabra clave estaba destinada a
- permitir el acceso a dispositivos de E / S asignados en memoria
- Permitir el uso de variables entre
setjmp
ylongjmp
- Permitir el uso de
sig_atomic_t
variables en manejadores de señales.
Aunque tanto C como 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 informe de defectos respectivo DR 476 (a C11) todavía se está revisando con C17.
Las operaciones sobre las volatile
variables no son atómicas , ni establecen una relación adecuada de suceder antes del subproceso. Esto se especifica en los estándares relevantes (C, C ++, POSIX , WIN32), 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.
Ejemplo de E / S mapeadas en memoria en C
En este ejemplo, el código establece el valor almacenado en foo
a 0
. Luego comienza a sondear ese valor repetidamente hasta que cambia a 255
:
static int foo;
void bar(void) {
foo = 0;
while (foo != 255)
;
}
Un compilador optimizador notará que ningún otro código puede cambiar el valor almacenado foo
y asumirá que permanecerá igual 0
en 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;
while (true)
;
}
Sin embargo, foo
podría representar una ubicación que puede ser cambiada por otros elementos del sistema informático 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 mucho, la situación más común).
Para evitar que el compilador optimice el código como se indicó anteriormente, volatile
se utiliza la palabra clave:
static volatile int foo;
void bar (void) {
foo = 0;
while (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 deberían ser preferidas 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 (antes de C11) ni la especificación C ++ (antes de C ++ 11) especifican un modelo de memoria de subprocesos múltiples, por lo que es posible que los volátiles no se comporten de manera determinista en los sistemas operativos / compiladores / CPU.
Comparación de optimización en C
Los siguientes programas en C y los ensamblados 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, se ve claramente que el código generado con volatile
objetos es más detallado, lo que lo hace más largo para volatile
que se pueda cumplir con la naturaleza de los objetos. La volatile
palabra clave evita que el compilador realice la 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 la memoria correspondiente. Sin la volatile
palabra clave, el compilador sabe que no es necesario volver a leer una variable de la memoria en cada uso, porque no debería haber ninguna escritura en su ubicación de memoria desde ningún otro hilo o proceso.
Comparación de ensamblajes | |
---|---|
Sin volatile palabra clave |
Con volatile palabra clave
|
# include <stdio.h>
int main() {
/* These variables will never be created on stack*/
int a = 10, b = 100, c = 0, d = 0;
/* "printf" will be called with arguments "%d" and
110 (the compiler computes the sum of a+b),
hence no overhead of performing addition at
run-time */
printf("%d", a + b);
/* This code will be removed via optimization, but
the impact of 'c' and 'd' becoming 100 can be
seen while calling "printf" */
a = b;
c = b;
d = b;
/* Compiler will generate code where printf is
called with arguments "%d" and 200 */
printf("%d", c + d);
return 0;
}
|
# include <stdio.h>
int main() {
volatile int a = 10, b = 100, c = 0, d = 0;
printf("%d", a + b);
a = b;
c = b;
d = b;
printf("%d", c + d);
return 0;
}
|
gcc -S -O3 -masm = intel noVolatileVar.c -o sin.s | gcc -S -O3 -masm = intel VolatileVar.c -o with.s |
.file "noVolatileVar.c"
.intel_syntax noprefix
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
sub rsp, 8
.cfi_def_cfa_offset 16
mov esi, 110
mov edi, OFFSET FLAT:.LC0
xor eax, eax
call printf
mov esi, 200
mov edi, OFFSET FLAT:.LC0
xor eax, eax
call printf
xor eax, eax
add rsp, 8
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (GNU) 4.8.2"
.section .note.GNU-stack,"",@progbits
|
.file "VolatileVar.c"
.intel_syntax noprefix
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
sub rsp, 24
.cfi_def_cfa_offset 32
mov edi, OFFSET FLAT:.LC0
mov DWORD PTR [rsp], 10
mov DWORD PTR [rsp+4], 100
mov DWORD PTR [rsp+8], 0
mov DWORD PTR [rsp+12], 0
mov esi, DWORD PTR [rsp]
mov eax, DWORD PTR [rsp+4]
add esi, eax
xor eax, eax
call printf
mov eax, DWORD PTR [rsp+4]
mov edi, OFFSET FLAT:.LC0
mov DWORD PTR [rsp], eax
mov eax, DWORD PTR [rsp+4]
mov DWORD PTR [rsp+8], eax
mov eax, DWORD PTR [rsp+4]
mov DWORD PTR [rsp+12], eax
mov esi, DWORD PTR [rsp+8]
mov eax, DWORD PTR [rsp+12]
add esi, eax
xor eax, eax
call printf
xor eax, eax
add rsp, 24
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (GNU) 4.8.2"
.section .note.GNU-stack,"",@progbits
|
C ++ 11
De acuerdo con el estándar ISO C ++ 11 , la palabra clave volátil solo está pensada para su uso para el 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.
En Java
El lenguaje de programación Java también tiene la volatile
palabra clave, pero se usa para un propósito algo diferente. Cuando se aplica a un campo, el calificador de Java volatile
proporciona las siguientes garantías:
- En todas las versiones de Java, hay un orden global en lecturas y escrituras de todas las variables volátiles (este orden global en volátiles es un orden parcial sobre el orden de sincronización más grande (que es un orden total sobre todas las acciones de sincronización )). Esto implica que cada hilo que acceda a un campo volátil leerá su valor actual antes de continuar, en lugar de (potencialmente) usar un valor almacenado en caché. (Sin embargo, no hay garantía sobre el orden relativo de lecturas y escrituras volátiles con lecturas y escrituras regulares, lo que significa que generalmente no es una construcción de subprocesos útil).
- En Java 5 o posterior, las lecturas y escrituras volátiles establecen una relación de suceder antes , muy similar a la adquisición y liberación de un mutex.
El uso volatile
puede ser más rápido que un bloqueo , pero no funcionará en algunas situaciones antes de Java 5. La gama de situaciones en las que lo volátil es efectivo se amplió en Java 5; en particular, el bloqueo con doble verificación ahora funciona correctamente.
C ª#
En C # , volatile
garantiza que el código que accede al campo no esté sujeto a algunas optimizaciones no seguras para subprocesos que pueden realizar el compilador, el CLR o el hardware. Cuando se marca un campo volatile
, se le indica al compilador que genere una "barrera de memoria" o "valla" a su alrededor, lo que evita el reordenamiento de instrucciones o el almacenamiento en caché vinculado al campo. Al leer un volatile
campo, el compilador genera un cercado de adquisición , que evita que otras lecturas y escrituras en el campo, incluidas las de otros subprocesos, se muevan antes del cercado. Al escribir en un volatile
campo, el compilador genera una barrera de liberación ; esta valla evita que otras lecturas y escrituras en el campo se muevan después de la valla.
Sólo los siguientes tipos se pueden marcar volatile
: todos los tipos de referencia, Single
, Boolean
, Byte
, SByte
, Int16
, UInt16
, Int32
, UInt32
, Char
, y todos los tipos enumerados con un tipo de subyacente Byte
, SByte
, Int16
, UInt16
, Int32
, o UInt32
. (No se incluyen valoran estructuras , así como los tipos primitivos Double
, Int64
, UInt64
y 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 utilizarse en su lugar.
En efecto, 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 un "medio cercado" (es decir, un cercado de adquisición solo evita el reordenamiento de instrucciones y el almacenamiento en caché que viene antes), VolatileRead
y VolatileWrite
genera un "cercado completo" que evitar 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 fuerza la escritura del valor en el campo en el punto de la llamada. Además, cualquier carga y almacenamiento de pedidos de programas anteriores debe ocurrir antes de la llamadaVolatileWrite
y cualquier carga y almacenamiento de pedidos de programas posteriores debe ocurrir después de la llamada. - El
Thread.VolatileRead
método fuerza la lectura del valor en el campo en el punto de la llamada. Además, cualquier carga y almacenamiento de pedidos de programas anteriores debe ocurrir antes de la llamadaVolatileRead
y cualquier carga y almacenamiento de pedidos de programas posteriores debe ocurrir después de la llamada.
Los métodos Thread.VolatileRead
y Thread.VolatileWrite
generan una Thread.MemoryBarrier
barrera completa llamando al método, que construye una barrera de memoria que funciona en ambas direcciones. Además de las motivaciones para usar una cerca completa dadas anteriormente, un problema potencial con la volatile
palabra clave que se resuelve usando una cerca completa generada por Thread.MemoryBarrier
es el siguiente: debido a la naturaleza asimétrica de las medias cercas, un volatile
campo con una instrucción de escritura seguida de una instrucción de lectura aún puede tener el orden de ejecución intercambiado por el compilador. Debido a que las vallas completas son simétricas, esto no es un problema al usarlas 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 todas las variables volatile
en una función también es útil para encontrar errores relacionados con el alias .
integer, volatile :: i ! When not defined volatile the following two lines of code are identical
write(*,*) i**2 ! Loads the variable i once from memory and multiplies that value times itself
write(*,*) i*i ! Loads the variable i twice from memory and multiplies those values
Al "profundizar" siempre en la memoria de un VOLATILE, el compilador de Fortran no puede reordenar las lecturas o escrituras en volátiles. Esto hace visible a otros hilos las acciones realizadas en este hilo y viceversa.
El uso de VOLATILE reduce e incluso puede evitar la optimización.