Una definición de volatile
volatile
le dice al compilador que el valor de la variable puede cambiar sin que el compilador lo sepa. Por lo tanto, el compilador no puede asumir que el valor no cambió solo porque el programa C parece no haberlo cambiado.
Por otro lado, significa que el valor de la variable puede ser requerido (leído) en otro lugar que el compilador no conoce, por lo tanto, debe asegurarse de que cada asignación a la variable se lleve a cabo realmente como una operación de escritura.
Casos de uso
volatile
se requiere cuando
- representando registros de hardware (o E / S mapeadas en memoria) como variables, incluso si el registro nunca se leerá, el compilador no debe simplemente saltarse la operación de escritura pensando "Programador estúpido. Intenta almacenar un valor en una variable que él / ella nunca volverá a leer. Él / ella no se dará cuenta si omitimos la escritura " Por el contrario, incluso si el programa nunca escribe un valor en la variable, su valor aún puede ser cambiado por el hardware.
- compartir variables entre contextos de ejecución (por ejemplo, ISR / programa principal) (ver la respuesta de @kkramo)
Efectos de volatile
Cuando se declara una variable, volatile
el compilador debe asegurarse de que cada asignación en el código del programa se refleje en una operación de escritura real, y que cada lectura en el código del programa lea el valor de la memoria (mmapped).
Para las variables no volátiles, el compilador supone que sabe si / cuando cambia el valor de la variable y puede optimizar el código de diferentes maneras.
Por un lado, el compilador puede reducir el número de lecturas / escrituras en la memoria, manteniendo el valor en los registros de la CPU.
Ejemplo:
void uint8_t compute(uint8_t input) {
uint8_t result = input + 2;
result = result * 2;
if ( result > 100 ) {
result -= 100;
}
return result;
}
Aquí, el compilador probablemente ni siquiera asignará RAM para la result
variable, y nunca almacenará los valores intermedios en ningún lugar que no sea en un registro de CPU.
Si result
fuera volátil, cada aparición result
en el código C requeriría que el compilador realice un acceso a RAM (o un puerto de E / S), lo que lleva a un rendimiento más bajo.
En segundo lugar, el compilador puede reordenar operaciones en variables no volátiles para el rendimiento y / o el tamaño del código. Ejemplo simple:
int a = 99;
int b = 1;
int c = 99;
podría ser reordenado a
int a = 99;
int c = 99;
int b = 1;
que puede guardar una instrucción de ensamblador porque el valor 99
no tendrá que cargarse dos veces.
Si a
, b
y c
fuera volátil, el compilador tendría que emitir instrucciones que asignen los valores en el orden exacto que se dan en el programa.
El otro ejemplo clásico es así:
volatile uint8_t signal;
void waitForSignal() {
while ( signal == 0 ) {
// Do nothing.
}
}
Si, en este caso, signal
no fuera así volatile
, el compilador 'pensaría' que while( signal == 0 )
puede ser un bucle infinito (porque signal
nunca será cambiado por el código dentro del bucle ) y podría generar el equivalente de
void waitForSignal() {
if ( signal != 0 ) {
return;
} else {
while(true) { // <-- Endless loop!
// do nothing.
}
}
}
Considerado manejo de volatile
valores
Como se indicó anteriormente, una volatile
variable puede introducir una penalización de rendimiento cuando se accede con más frecuencia de la que realmente se requiere. Para mitigar este problema, puede "desestabilizar" el valor mediante la asignación a una variable no 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
}
Esto puede ser especialmente beneficioso en la ISR en el que desea ser tan rápido como sea posible sin el acceso a las mismas tarjetas de memoria o varias veces cuando se sabe que no es necesario porque el valor no cambiará mientras el ISR se está ejecutando. Esto es común cuando el ISR es el 'productor' de valores para la variable, como sysTickCount
en el ejemplo anterior. En un AVR, sería especialmente doloroso que la función doSysTick()
acceda a los mismos cuatro bytes en la memoria (cuatro instrucciones = 8 ciclos de CPU por acceso sysTickCount
) cinco o seis veces en lugar de solo dos veces, porque el programador sabe que el valor no será ser cambiado de algún otro código mientras se doSysTick()
ejecuta.
Con este truco, básicamente hace exactamente lo mismo que hace el compilador para las variables no volátiles, es decir, las lee de la memoria solo cuando es necesario, guarda el valor en un registro durante un tiempo y vuelve a escribir en la memoria solo cuando tiene que hacerlo. ; pero esta vez, usted sabe mejor que el compilador si / cuando deben realizarse lecturas / escrituras , por lo que libera al compilador de esta tarea de optimización y lo hace usted mismo.
Limitaciones de volatile
Acceso no atómico
volatile
no no proporcionar acceso a las variables atómica de varias palabras. Para esos casos, deberá proporcionar la exclusión mutua por otros medios, además del uso volatile
. En el AVR, puede usar ATOMIC_BLOCK
desde <util/atomic.h>
o cli(); ... sei();
llamadas simples . Las macros respectivas también actúan como una barrera de memoria, lo cual es importante cuando se trata del orden de los accesos:
Orden de ejecución
volatile
impone un estricto orden de ejecución solo con respecto a otras variables volátiles. Esto significa que, por ejemplo
volatile int i;
volatile int j;
int a;
...
i = 1;
a = 99;
j = 2;
se garantiza que primero asigne 1 a i
y luego asigne 2 a j
. Sin embargo, se no garantiza que a
será asignado en el medio; el compilador puede hacer esa asignación antes o después del fragmento de código, básicamente en cualquier momento hasta la primera lectura (visible) de a
.
Si no fuera por la barrera de memoria de las macros mencionadas anteriormente, el compilador podría traducir
uint32_t x;
cli();
x = volatileVar;
sei();
a
x = volatileVar;
cli();
sei();
o
cli();
sei();
x = volatileVar;
(En aras de la exhaustividad, debo decir que las barreras de memoria, como las implicadas por las macros sei / cli, en realidad pueden obviar el uso de volatile
, si todos los accesos están entre corchetes con estas barreras).