volátil (programação de computador) - volatile (computer programming)
Na programação de computadores , principalmente no C , C ++ , C # e Java linguagens de programação , o volátil palavra-chave indica que um valor podem variar entre diferentes acessos, mesmo que não parece ser modificado. Esta palavra-chave evita que um compilador de otimização otimize leituras ou gravações subsequentes e, portanto, reutilize incorretamente um valor obsoleto ou omita gravações. Valores voláteis surgem principalmente no acesso hardware ( memória mapeada I / O ), onde ler ou escrever para a memória é usado para se comunicar com dispositivos periféricos , e em rosca , onde um segmento diferente pode ter modificado um valor.
Apesar de ser uma palavra-chave comum, o comportamento de volatile
difere significativamente entre as linguagens de programação e é facilmente mal interpretado. Em C e C ++, é um qualificador de tipo , como const
, e é uma propriedade do tipo . Além disso, em C e C ++ não funciona na maioria dos cenários de threading e esse uso é desencorajado. Em Java e C #, é uma propriedade de uma variável e indica que o objeto ao qual a variável está ligada pode sofrer mutação e se destina especificamente a threading. Na linguagem de programação D , há uma palavra-chave separada shared
para o uso do encadeamento, mas não volatile
existe nenhuma palavra-chave.
Em C e C ++
Em C e, conseqüentemente, C ++, a volatile
palavra - chave destinava-se a
- permitir acesso a dispositivos de E / S mapeados em memória
- permitem o uso de variáveis entre
setjmp
elongjmp
- permitir o uso de
sig_atomic_t
variáveis em manipuladores de sinal.
Embora pretendido por C e C ++, os padrões C falham em expressar que a volatile
semântica se refere ao lvalue, não ao objeto referenciado. O respectivo relatório de defeito DR 476 (a C11) ainda está em revisão com C17.
As operações em volatile
variáveis não são atômicas , nem estabelecem um relacionamento acontece antes adequado para encadeamento. Isso é especificado nos padrões relevantes (C, C ++, POSIX , WIN32) e variáveis voláteis não são threadsafe na grande maioria das implementações atuais. Portanto, o uso de volatile
palavras-chave como um mecanismo de sincronização portátil é desencorajado por muitos grupos C / C ++.
Exemplo de E / S mapeada por memória em C
Neste exemplo, o código define o valor armazenado em foo
como 0
. Em seguida, ele começa a pesquisar esse valor repetidamente até que ele mude para 255
:
static int foo;
void bar(void) {
foo = 0;
while (foo != 255)
;
}
Um compilador otimizado notará que nenhum outro código pode alterar o valor armazenado em foo
e presumirá que ele permanecerá igual a 0
todo o tempo. O compilador, portanto, substituirá o corpo da função por um loop infinito semelhante a este:
void bar_optimized(void) {
foo = 0;
while (true)
;
}
No entanto, foo
pode representar um local que pode ser alterado por outros elementos do sistema de computador a qualquer momento, como um registro de hardware de um dispositivo conectado à CPU . O código acima nunca detectaria tal mudança; sem a volatile
palavra - chave, o compilador assume que o programa atual é a única parte do sistema que pode alterar o valor (que é de longe a situação mais comum).
Para evitar que o compilador otimize o código como acima, a volatile
palavra-chave é usada:
static volatile int foo;
void bar (void) {
foo = 0;
while (foo != 255)
;
}
Com esta modificação, a condição do loop não será otimizada e o sistema detectará a mudança quando ela ocorrer.
Geralmente, existem operações de barreira de memória disponíveis em plataformas (que são expostas em C ++ 11) que devem ser preferidas em vez de voláteis, pois permitem que o compilador execute uma melhor otimização e, mais importante, garantem o comportamento correto em cenários multi-threaded; nem a especificação C (antes de C11) nem a especificação C ++ (antes de C ++ 11) especifica um modelo de memória multi-threaded, portanto, volátil pode não se comportar deterministicamente em sistemas operacionais / compiladores / CPUs.
Comparação de otimização em C
Os programas C a seguir e os assemblies que os acompanham demonstram como a volatile
palavra - chave afeta a saída do compilador. O compilador neste caso foi o GCC .
Ao observar o código do assembly, é claramente visível que o código gerado com os volatile
objetos é mais detalhado, tornando-o mais longo para que a natureza dos volatile
objetos possa ser cumprida. A volatile
palavra-chave evita que o compilador execute a otimização do código que envolve objetos voláteis, garantindo assim que cada atribuição e leitura de variável volátil tenha um acesso de memória correspondente. Sem a volatile
palavra-chave, o compilador sabe uma variável não precisa ser reler a partir da memória em cada uso, pois não deve haver quaisquer gravações para o seu local de memória de qualquer outro processo ou segmento.
Comparação de montagem | |
---|---|
Sem volatile palavra-chave |
Com volatile palavra-chave
|
# 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 without.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 acordo com o padrão ISO C ++ 11 , a palavra-chave volatile destina-se apenas ao uso para acesso ao hardware; não o use para comunicação entre threads. Para comunicação entre threads, a biblioteca padrão fornece std::atomic<T>
modelos.
Em Java
A linguagem de programação Java também tem a volatile
palavra - chave, mas é usada para um propósito um pouco diferente. Quando aplicado a um campo, o qualificador Java volatile
oferece as seguintes garantias:
- Em todas as versões do Java, há uma ordenação global em leituras e gravações de todas as variáveis voláteis (esta ordenação global no voláteis é uma ordem parcial sobre a maior ordem de sincronização (que é uma ordem total sobre todas as acções de sincronização )). Isto implica que cada fio de acesso a um campo volátil irá ler seu valor atual antes de continuar, em vez de (potencialmente) usando um valor em cache. (No entanto, não há garantia sobre a ordem relativa de leituras e gravações voláteis com leituras e gravações regulares, o que significa que geralmente não é uma construção de threading útil.)
- No Java 5 ou posterior, as leituras e gravações voláteis estabelecem um relacionamento acontece antes , muito parecido com a aquisição e liberação de um mutex.
O uso volatile
pode ser mais rápido do que um bloqueio , mas não funcionará em algumas situações antes do Java 5. A gama de situações em que o volátil é eficaz foi expandida no Java 5; Em particular, verificou duas vezes trancar agora funciona corretamente.
Em C #
Em C # , volatile
garante que o código que acessa o campo não está sujeito a algumas otimizações inseguras de thread que podem ser executadas pelo compilador, CLR ou pelo hardware. Quando um campo é marcado volatile
, o compilador é instruído a gerar uma "barreira de memória" ou "cerca" em torno dele, o que evita o reordenamento de instruções ou o armazenamento em cache vinculado ao campo. Ao ler um volatile
campo, o compilador gera uma cerca de aquisição , que evita que outras leituras e gravações no campo, incluindo aquelas em outras threads, sejam movidas antes da cerca. Ao gravar em um volatile
campo, o compilador gera uma cerca de liberação ; esta cerca evita que outras leituras e gravações no campo sejam movidas após a cerca.
Apenas os seguintes tipos podem ser marcados volatile
: todos os tipos de referência, Single
, Boolean
, Byte
, SByte
, Int16
, UInt16
, Int32
, UInt32
, Char
, e todos os tipos enumerados com um tipo subjacente de Byte
, SByte
, Int16
, UInt16
, Int32
, ou UInt32
. (Isto exclui valorizar estruturas , bem como os tipos de primitivas Double
, Int64
, UInt64
e Decimal
.)
O uso da volatile
palavra - chave não suporta campos que são passados por referência ou variáveis locais capturadas ; nesses casos, Thread.VolatileRead
e Thread.VolatileWrite
deve ser usado em seu lugar.
Na verdade, esses métodos desabilitam algumas otimizações geralmente realizadas pelo compilador C #, pelo compilador JIT ou pela própria CPU. As garantias fornecidas por Thread.VolatileRead
e Thread.VolatileWrite
são um superconjunto das garantias fornecidas pela volatile
palavra-chave: em vez de gerar uma "meia cerca" (ou seja, uma cerca de aquisição apenas evita a reordenação de instruções e o armazenamento em cache que vem antes dela), VolatileRead
e VolatileWrite
gera uma "cerca cheia" que evitar a reordenação de instruções e o armazenamento em cache desse campo em ambas as direções. Esses métodos funcionam da seguinte maneira:
- O
Thread.VolatileWrite
método força o valor no campo a ser gravado no ponto da chamada. Além disso, todos os carregamentos e armazenamentos de pedidos de programas anteriores devem ocorrer antes da chamada paraVolatileWrite
e quaisquer carregamentos e armazenamentos de pedidos de programas posteriores devem ocorrer após a chamada. - O
Thread.VolatileRead
método força o valor no campo a ser lido no ponto da chamada. Além disso, quaisquer cargas e lojas anteriores de ordem programa deve ocorrer antes da chamada paraVolatileRead
e quaisquer cargas e lojas posteriores de ordem programa deve ocorrer após a chamada.
O Thread.VolatileRead
e Thread.VolatileWrite
métodos de gerar uma cerca cheia chamando o Thread.MemoryBarrier
método, que constrói uma barreira de memória que funciona em ambas as direções. Além das motivações para usar uma cerca completa fornecidas acima, um problema potencial com a volatile
palavra - chave que é resolvido usando uma cerca completa gerada por Thread.MemoryBarrier
é o seguinte: devido à natureza assimétrica das meias-cercas, um volatile
campo com uma instrução de escrita seguida por uma instrução de leitura ainda pode ter a ordem de execução trocada pelo compilador. Como as cercas completas são simétricas, isso não é um problema durante o uso Thread.MemoryBarrier
.
Em Fortran
VOLATILE
faz parte do padrão Fortran 2003 , embora a versão anterior o suportasse como uma extensão. Criar todas as variáveis volatile
em uma função também é útil para localizar bugs relacionados a aliasing .
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
Sempre "pesquisando" a memória de um VOLÁTIL, o compilador Fortran é impedido de reordenar leituras ou gravações em voláteis. Isso torna visível para outras ações de encadeamentos feitas neste encadeamento e vice-versa.
O uso de VOLATILE reduz e pode até impedir a otimização.