Uma definição de volatile
volatile
informa ao compilador que o valor da variável pode ser alterado sem o conhecimento do compilador. Portanto, o compilador não pode assumir que o valor não foi alterado apenas porque o programa C parece não o ter alterado.
Por outro lado, significa que o valor da variável pode ser necessário (lido) em algum outro lugar que o compilador não conheça, portanto, ele deve garantir que todas as atribuições à variável sejam realmente executadas como uma operação de gravação.
Casos de uso
volatile
é necessário quando
- representando registros de hardware (ou E / S mapeada na memória) como variáveis - mesmo que o registro nunca seja lido, o compilador não deve apenas ignorar a operação de gravação pensando em "Programador estúpido. Tenta armazenar um valor em uma variável que ele / ela nunca lerá de volta. Ele nem notará se omitirmos a gravação. " Por outro lado, mesmo que o programa nunca grave um valor na variável, seu valor ainda poderá ser alterado pelo hardware.
- compartilhando variáveis entre contextos de execução (por exemplo, ISRs / programa principal) (consulte a resposta do @ kkramo)
Efeitos de volatile
Quando uma variável é declarada, volatile
o compilador deve garantir que todas as atribuições a ele no código do programa sejam refletidas em uma operação de gravação real e que toda leitura no código do programa leia o valor da memória (mmapped).
Para variáveis não voláteis, o compilador assume que sabe se / quando o valor da variável é alterado e pode otimizar o código de maneiras diferentes.
Por um lado, o compilador pode reduzir o número de leituras / gravações na memória, mantendo o valor nos registros da CPU.
Exemplo:
void uint8_t compute(uint8_t input) {
uint8_t result = input + 2;
result = result * 2;
if ( result > 100 ) {
result -= 100;
}
return result;
}
Aqui, o compilador provavelmente nem alocará RAM para a result
variável e nunca armazenará os valores intermediários em nenhum lugar, exceto em um registro da CPU.
Se result
fosse volátil, todas as ocorrências result
no código C exigiriam que o compilador executasse um acesso à RAM (ou uma porta de E / S), levando a um desempenho mais baixo.
Em segundo lugar, o compilador pode reordenar as operações em variáveis não voláteis para desempenho e / ou tamanho do código. Exemplo simples:
int a = 99;
int b = 1;
int c = 99;
poderia ser reordenado para
int a = 99;
int c = 99;
int b = 1;
o que pode salvar uma instrução do assembler porque o valor 99
não precisará ser carregado duas vezes.
Se a
, b
e c
foram volátil o compilador teria que emitir instruções que atribuem os valores na ordem exata como eles são dadas no programa.
O outro exemplo clássico é assim:
volatile uint8_t signal;
void waitForSignal() {
while ( signal == 0 ) {
// Do nothing.
}
}
Se, nesse caso, signal
não fosse volatile
, o compilador "pensaria" que while( signal == 0 )
pode ser um loop infinito (porque signal
nunca será alterado pelo código dentro do loop ) e poderá gerar o equivalente a
void waitForSignal() {
if ( signal != 0 ) {
return;
} else {
while(true) { // <-- Endless loop!
// do nothing.
}
}
}
Manuseio atencioso de volatile
valores
Conforme mencionado acima, uma volatile
variável pode introduzir uma penalidade no desempenho quando é acessada com mais frequência do que o necessário. Para atenuar esse problema, você pode "não ser volátil" o valor atribuindo a uma variável não volátil, como
volatile uint32_t sysTickCount;
void doSysTick() {
uint32_t ticks = sysTickCount; // A single read access to sysTickCount
ticks = ticks + 1;
setLEDState( ticks < 500000L );
if ( ticks >= 1000000L ) {
ticks = 0;
}
sysTickCount = ticks; // A single write access to volatile sysTickCount
}
Isso pode ser especialmente benéfico nos ISRs, nos quais você deseja ser o mais rápido possível, sem acessar o mesmo hardware ou memória várias vezes quando você sabe que não é necessário, pois o valor não será alterado enquanto o ISR estiver em execução. Isso é comum quando o ISR é o 'produtor' de valores para a variável, como sysTickCount
no exemplo acima. Em um AVR, seria especialmente doloroso fazer com que a função doSysTick()
acesse os mesmos quatro bytes na memória (quatro instruções = 8 ciclos de CPU por acesso a sysTickCount
) cinco ou seis vezes em vez de apenas duas vezes, porque o programador sabe que o valor não será ser alterado de outro código enquanto ele doSysTick()
é executado.
Com esse truque, você basicamente faz exatamente o mesmo que o compilador faz para variáveis não voláteis, ou seja, lê-las da memória somente quando necessário, mantém o valor em um registro por algum tempo e grava na memória somente quando necessário. ; mas desta vez, você sabe melhor que o compilador se / quando as leituras / gravações devem ocorrer, portanto, você libera o compilador dessa tarefa de otimização e faz você mesmo.
Limitações de volatile
Acesso não atômico
volatile
se não fornecer acesso atômica para variáveis com várias palavras. Para esses casos, você precisará fornecer exclusão mútua por outros meios, além do uso volatile
. No AVR, você pode usar ATOMIC_BLOCK
a partir <util/atomic.h>
ou simples cli(); ... sei();
chamadas. As respectivas macros também funcionam como uma barreira de memória, o que é importante quando se trata da ordem dos acessos:
Ordem de execução
volatile
impõe ordem estrita de execução apenas com relação a outras variáveis voláteis. Isso significa que, por exemplo
volatile int i;
volatile int j;
int a;
...
i = 1;
a = 99;
j = 2;
é garantido atribuir primeiro 1 a i
e depois atribuir 2 a j
. No entanto, é não garantiu que a
será atribuído no meio; o compilador pode fazer essa atribuição antes ou depois do trecho de código, basicamente a qualquer momento até a primeira leitura (visível) a
.
Se não fosse a barreira da memória das macros mencionadas acima, o compilador poderia traduzir
uint32_t x;
cli();
x = volatileVar;
sei();
para
x = volatileVar;
cli();
sei();
ou
cli();
sei();
x = volatileVar;
(Por uma questão de completude, devo dizer que as barreiras de memória, como as implícitas nas macros sei / cli, podem realmente impedir o uso de volatile
, se todos os acessos estiverem entre colchetes com essas barreiras.)