Volatile

Volatile es una palabra clave usada para pedirle al compilador que desactive las optimizaciones en una variable, incluso cuando estas estén activas. Sus principales usos son para impedir que el compilador elimine asignaciones "innecesarias" sobre variables, o accesos repetidos a una variable. ¿En qué circunstancias esto es importante? Cuando la variable puede ser accedida por múltiples hilos, por ejemplo.

La palabra clave volatile en C es una herramienta muy específica que nos permite indicarle al compilador que no debe optimizar el acceso a una variable determinada. Esto puede parecer un detalle menor, pero en ciertos contextos es fundamental para que nuestro programa funcione correctamente.

Cuando escribimos código en C, el compilador suele aplicar optimizaciones para hacer que el programa sea más eficiente. Por ejemplo, si tenemos una variable global llamada value y en una función hacemos varias asignaciones consecutivas a esa variable, como asignarle los valores 1, 2, 3, 4 y 5, el compilador puede darse cuenta de que las primeras cuatro asignaciones no tienen efecto visible y eliminarlas, dejando solo la última asignación. Esto ocurre porque, desde su punto de vista, las asignaciones intermedias son innecesarias si no se usan para nada más.

Sin embargo, hay situaciones en las que necesitamos que todas esas asignaciones se ejecuten tal cual, sin que el compilador las suprima o las reordene. Ahí es donde entra en juego volatile. Al declarar una variable como volatile, le estamos diciendo al compilador que no puede optimizar el acceso a esa variable, que debe realizar todas las lecturas y escrituras exactamente como aparecen en el código, sin omitir ninguna.

Esto es especialmente importante en programación multitarea o cuando trabajamos con hardware directamente, como en sistemas embebidos o arquitecturas ARM. Por ejemplo, en placas como la Raspberry Pi, para controlar dispositivos como LEDs o puertos serie, escribimos en direcciones de memoria específicas que no corresponden a la memoria RAM convencional, sino que están mapeadas a hardware. En estos casos, cada escritura en esa dirección de memoria tiene un efecto real, como enviar un byte por el puerto serie.

Imaginemos que tenemos un puntero serial que apunta a una dirección especial, por ejemplo 0x9000000, que corresponde al puerto serie. Si escribimos consecutivamente los caracteres 'H', 'O', 'L', 'A' y un salto de línea en esa dirección, queremos que cada escritura se realice para que el mensaje se envíe correctamente. Si no usamos volatile, el compilador podría optimizar y eliminar algunas de esas escrituras, pensando que son redundantes, y el mensaje no se enviaría completo.

Por eso, declaramos el puntero como un puntero a volatile para asegurarnos de que cada asignación se ejecute. Así, aunque compilemos con optimizaciones agresivas, el compilador respetará todas las escrituras y el hardware recibirá cada byte tal como esperamos.

Un ejemplo sencillo en código para ilustrar esto sería:

volatile char *serial = (volatile char *)0x9000000;

void printola() {
    serial[0] = 'H';
    serial[0] = 'O';
    serial[0] = 'L';
    serial[0] = 'A';
    serial[0] = '\n';
}

int main() {
    printola();
    return 0;
}

En este fragmento, cada asignación a serial[0] envía un carácter al puerto serie. Gracias a volatile, el compilador no eliminará ni reordenará estas asignaciones, garantizando que el mensaje HOLA\n se transmita correctamente.

En resumen, volatile es una palabra clave que debemos usar cuando trabajamos con variables que pueden cambiar fuera del control del programa, como registros de hardware o variables compartidas en entornos multitarea, para evitar que el compilador aplique optimizaciones que alteren el comportamiento esperado. Aunque no es común en programas simples, conocer su uso es esencial para programar en sistemas embebidos o en arquitecturas como ARM.

Lista de reproducción
  1. 1
    Instalar CodeBlocks
    15 minutos
  2. 2
    Funciones y hola mundo
    17 minutos
  3. 3
    Variables y tipos de datos
    17 minutos
  4. 4
    Condicionales y operadores lógicos
    16 minutos
  5. 5
    Bucles
    11 minutos
  6. 6
    Punteros
    12 minutos
  7. 7
    Arrays
    14 minutos
  8. 8
    Estructuras
    12 minutos
  9. 9
    Otras construcciones de C
    9 minutos
  10. 10
    Memoria dinámica
    8 minutos
  11. 11
    El preprocesador (parte 1)
    10 minutos
  12. 12
    El preprocesador (parte 2)
    9 minutos
  13. 13
    Archivos de cabecera y múltiples .c (parte 1)
    8 minutos
  14. 14
    Archivos de cabecera y múltiples .c (parte 2)
    9 minutos
  15. 15
    C desde la línea de comandos (parte 1)
    8 minutos
  16. 16
    C desde la línea de comandos (parte 2)
    9 minutos
  17. 17
    Break y continue
    10 minutos
  18. 18
    Goto
    13 minutos
  19. 19
    Manipulación de bits
    15 minutos
  20. 20
    Máscaras de bit
    19 minutos
  21. 21
    Archivos (1): fopen y fclose
    13 minutos
  22. 22
    Archivos (2): leer con fgetc
    9 minutos
  23. 23
    Archivos (3): fseek y ftell
    11 minutos
  24. 24
    Archivos (4): leer con fgets
    9 minutos
  25. 25
    Archivos (5): fputc y fputs
    7 minutos
  26. 26
    Archivos (6): volcar en archivos con fwrite
    10 minutos
  27. 27
    Archivos (7): fread, fwrite y los arrays
    10 minutos
  28. 28
    Archivos (8): entrada estándar y salida estándar
    9 minutos
  29. 29
    Archivos (9): buffers
    14 minutos
  30. 30
    Archivos (y 10): otras funciones útiles con archivos
    5 minutos
  31. 31
    printf (1)
    18 minutos
  32. 32
    printf (parte 2)
    12 minutos
  33. 33
    scanf (parte 1)
    17 minutos
  34. 34
    scanf (parte 2)
    17 minutos
  35. 35
    fprintf, sprintf y snprintf
    8 minutos
  36. 36
    Tipos de datos opacos
    13 minutos
  37. 37
    Bibliotecas estáticas
    13 minutos
  38. 38
    Bibliotecas dinámicas
    15 minutos
  39. 39
    Más flags: i mayúscula (include), wall, werror, pedantic...
    12 minutos
  40. 40
    pkg-config
    12 minutos
  41. 41
    Make
    17 minutos
  42. 42
    GDB
    21 minutos
  43. 43
    Variables globales
    6 minutos
  44. 44
    extern
    9 minutos
  45. 45
    Funciones variádicas
    12 minutos
  46. 46
    El optimizador de GCC y la opción -O
    12 minutos
  47. 47
    Volatile
    6 minutos