Qu'est-ce que volatile (qualificateur de type) ?

Qualificateur de type du C qui indique au compilateur que la valeur d'une variable peut changer hors de la vue du programme (par un périphérique, un gestionnaire d'interruption, etc.), ou que les accès eux-mêmes ont un sens. Les accès à un objet volatile ne sont ni supprimés ni fusionnés : ils sont effectués autant de fois et dans l'ordre où ils sont écrits. On l'utilise pour les registres d'E/S mappées en mémoire et pour les indicateurs partagés avec les gestionnaires d'interruption. Il ne rend pas les opérations atomiques et ne garantit pas l'ordre vis-à-vis des autres cœurs.

Nom japonais: volatile(型修飾子)

« volatile (qualificateur de type) » apparaît dans 7 questions d'entraînement réparties sur 6 thèmes, dont Bases de la programmation en C, Système d'exploitation et matériel, Multicœur et synchronisation.

Domaine: Bases de la programmation en C

Ce qui se passe sans lui

Dans une boucle comme while (flag == 0) { }, si seul un gestionnaire d'interruption modifie flag, le compilateur peut juger que flag ne peut pas changer dans la boucle, ne le lire qu'une fois et produire une boucle infinie. La même chose peut se produire dans une boucle qui attend un registre d'état de périphérique, et des écritures successives dans le même registre peuvent être fusionnées en une seule. Avec volatile, les lectures et écritures sont réellement effectuées autant de fois qu'elles sont écrites dans le code.

Lire les déclarations

volatile uint32_t *p est un « pointeur sur un uint32_t volatile » : les accès à *p ne sont plus supprimés. volatile uint32_t *const p ajoute le sens que le pointeur lui-même ne peut pas être modifié (il désigne toujours le même registre). Les macros qui convertissent une adresse en pointeur et la déréférencent, comme #define REG (*(volatile uint32_t *)0x40020014), sont aussi très utilisées.

Ce que volatile ne fait pas

volatile ne rend pas atomique une opération de lecture-modification-écriture : une interruption au milieu de *reg |= mask; peut faire disparaître la modification d'un autre bit. Une valeur trop large pour être lue ou écrite en une fois, comme une variable de 32 bits sur un microcontrôleur 8 bits, peut être lue à moitié mise à jour si une interruption survient entre-temps. Pour synchroniser des threads, on utilise des mécanismes qui garantissent l'atomicité et l'ordre, comme _Atomic de C11 ou les verrous.

S'entraîner : questions sur volatile (qualificateur de type)

En C, pourquoi ajouter seulement volatile à une variable drapeau échangée entre threads est-il insuffisant comme synchronisation ?

  1. AParce qu’une variable volatile ne peut plus être lue par les autres threads
  2. BParce que volatile ne fait que restreindre les optimisations du compilateur, sans garantir l’indivisibilité des opérations ni l’ordre de visibilité pour les autres cœurs
  3. CParce qu’avec volatile, la variable est enregistrée sur disque et devient lente
Afficher la réponse et l'explication

Réponse : B. Parce que volatile ne fait que restreindre les optimisations du compilateur, sans garantir l’indivisibilité des opérations ni l’ordre de visibilité pour les autres cœurs

volatile indique au compilateur de ne pas supprimer ni regrouper les lectures et écritures de la variable ; il était à l’origine destiné à la manipulation des registres matériels. Il n’empêche pas le CPU de réordonner les opérations mémoire et ne rend pas indivisibles des opérations comme counter++. Depuis C11 et C++11, _Atomic et std::atomic garantissent à la fois l’indivisibilité et l’ordre mémoire.

Questions fréquentes

Peut-on utiliser const et volatile ensemble ?

Oui. const volatile signifie « le programme ne le modifie pas, mais il change pour des raisons extérieures : il faut le relire à chaque fois », et s'utilise par exemple pour les registres d'état en lecture seule.

volatile ralentit-il le code ?

Pour la variable concernée, l'optimisation qui consiste à garder sa valeur dans un registre devient impossible : les accès augmentent et le code peut ralentir. C'est pourquoi on ne l'applique qu'aux endroits nécessaires, comme les registres des périphériques et les variables partagées avec les gestionnaires d'interruption, et non à toutes les variables.

« volatile (qualificateur de type) » dans les explications

Questions mentionnant « volatile (qualificateur de type) » (7)

  1. Pour lire le registre d’état d’un périphérique par entrées/sorties mappées en mémoire comme ci-dessus, pourquoi ajouter volatile et configurer cette zone comme non cachée ?
  2. En C, pourquoi ajouter seulement volatile à une variable drapeau échangée entre threads est-il insuffisant comme synchronisation ?
  3. Dans les microcontrôleurs notamment, quelle méthode attribue aux registres des périphériques des adresses dans le même espace que la mémoire et les manipule avec les instructions ordinaires de lecture et d'écriture mémoire ?
  4. Lorsque le programme ci-dessus est compilé avec optimisation, led_on() peut ne pas être appelée même si l'interruption de temporisateur survient. Quelle combinaison de cause et de remède est correcte ?
  5. Dans les programmes embarqués, une variable qui reçoit la valeur d'un registre de périphérique de 32 bits est souvent déclarée avec le type uint32_t de stdint.h plutôt qu'avec unsigned int. Quelle en est la raison principale ?
  6. Un registre de sortie de microcontrôleur est déclaré et utilisé comme ci-dessus. Quelle description de cette déclaration est correcte ?
  7. En mesurant la vitesse de calcul avec la petite boucle ci-dessus, le temps reste presque nul même pour N grand. Quelle est la cause la plus probable ?

Termes associés

Variable Gestionnaire d'interruption UART Interruption Microcontrôleur Pointeur Microbenchmark Alignement et remplissage (padding) Allocation dynamique de mémoire Champ de bits (bit-field)

Retour au glossaire