Una definición de la volatile
volatile
le dice al compilador que el valor de la variable puede cambiar sin que el compilador sepa. Por lo tanto el compilador no puede asumir el valor no cambia sólo porque el programa en C no parece haber cambiado.
Por otro lado, significa que el valor de la variable puede ser requerida (leer) en algún otro lugar que el compilador no sabe nada, por lo tanto debe asegurarse de que cada asignación a la variable que realmente se lleva a cabo como una operación de escritura.
Casos de uso
volatile
es necesario cuando
- en representación de los registros de hardware (o memory-mapped I/O) como variables - incluso si el registro nunca será leída, el compilador no sólo debe omitir la operación de escritura, el pensamiento de "Estúpido programador. Intenta almacenar un valor en una variable a la que él/ella no será nunca jamás vuelva a leer. Él/ella aun no observe si omitimos la escritura." Conversly, incluso si el programa nunca se escribe un valor a la variable, su valor puede ser cambiada por hardware.
- compartir variables entre los contextos de ejecución (por ejemplo, ISRs/programa principal) (ver @kkramo la respuesta)
Efectos de la volatile
Cuando se declara una variable volatile
el compilador debe asegurarse de que toda asignación a en el código del programa se refleja en una operación de escritura, y que cada lectura de código de programa lee el valor de (mmapped) de la memoria.
Para los no-volátil variables, el compilador asume que se conoce a si/cuando el valor de la variable cambia y puede optimizar el código en diferentes maneras.
Para uno, el compilador puede reducir el número de lecturas/escrituras a la memoria, manteniendo el valor de 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 no incluso asignar memoria para el result
variable, y nunca va a almacenar los valores intermedios en cualquier lugar, pero en un registro de CPU.
Si result
era volátil, cada ocurrencia de result
en el código de C requeriría el compilador para realizar un acceso a la memoria RAM (o un puerto de e/S), que conduce a un bajo rendimiento.
En segundo lugar, el compilador puede re-orden de operaciones no volátil variables de rendimiento y/o el tamaño del código. Ejemplo sencillo:
int a = 99;
int b = 1;
int c = 99;
podría ser re-orden
int a = 99;
int c = 99;
int b = 1;
que puede salvar a un ensamblador de instrucción debido a que el valor de 99
no tiene que ser cargado dos veces.
Si a
, b
y c
fueron volátiles, el compilador tendría que emitir instrucciones que asignar los valores en el mismo orden que están en el programa.
Otro ejemplo clásico es como este:
volatile uint8_t signal;
void waitForSignal() {
while ( signal == 0 ) {
// Do nothing.
}
}
Si, en este caso, signal
fueron no volatile
, el compilador podría 'pensar' que while( signal == 0 )
puede ser un bucle infinito (porque signal
nunca será cambiado por el código dentro del bucle) y puede generar el equivalente de
void waitForSignal() {
if ( signal != 0 ) {
return;
} else {
while(true) { // <-- Endless loop!
// do nothing.
}
}
}
Considerado el manejo de volatile
valores
Como se indicó anteriormente, volatile
variable puede introducir una penalización de rendimiento cuando se accede más a menudo de lo que realmente se requiere. Para mitigar este problema, puede que "no volátil" el valor de la asignación a un no-volátil variable, 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 el ISR donde quiero ser tan rápido como sea posible acceder a el mismo hardware o en la memoria varias veces cuando usted sabe que no es necesario porque el valor no va a cambiar mientras el ISR se está ejecutando. Esto es común cuando el ISR es el "productor" de los valores de la variable, al igual que el sysTickCount
en el ejemplo anterior. En un AVR sería especialmente doloroso para tener la función doSysTick()
de acceso a la misma de cuatro bytes en la memoria (cuatro instrucciones = 8 ciclos de CPU por el acceso a la sysTickCount
) cinco o seis veces en lugar de sólo dos veces, porque el programador sabe que el valor no se pueden cambiar de algún otro código, mientras que su/su doSysTick()
pistas.
Con este truco, básicamente, de hacer exactamente lo mismo que el compilador hace para no volátil variables, es decir, la lectura de la memoria sólo cuando se tiene que, de mantener el valor en un registro durante algún tiempo y volver a escribir de memoria sólo cuando se tiene; pero, en este momento, usted sabe mejor que el compilador si/cuando lee/escribe debe suceder, por lo que aliviar el compilador de esta tarea de optimización y hacerlo usted mismo.
Limitaciones de volatile
No atómica de acceso
volatile
¿ no proporcionar atómica de acceso a la multi-word variables. Para esos casos, usted tendrá que proporcionar la exclusión mutua por otros medios, además de para el uso de volatile
. En el AVR, puede utilizar ATOMIC_BLOCK
de <util/atomic.h>
o simple cli(); ... sei();
llamadas. Los respectivos macros actuar como una barrera de memoria, que también es importante cuando se trata de la orden de los accesos:
El orden de ejecución de
volatile
impone una estricta orden de ejecución sólo 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;
está garantizado el primer asignar 1 i
y , a continuación, asignar 2 j
. Sin embargo, no se garantiza que a
será asignado entre ellos, el compilador puede hacer la tarea antes o después de que el fragmento de código, básicamente, en cualquier momento hasta el primer (visible) lectura de a
.
Si no fuera por la memoria de la barrera de los mencionados macros, el compilador sería permitido 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 la memoria de barreras, como las implícitas por el sei/cli macros, en realidad puede obviar el uso de volatile
, si todos los accesos están entre corchetes con estas barreras.)