1. Pourquoi utiliser volatile ?
Le mot-clé volatile en C/C++ correspond à const et sert à modifier des variables, généralement pour établir une barrière mémoire au niveau du langage. Voici l'explication de Bjarne Stroustrup (BS) du modificateur volatile dans « The C++ Programming Language » :
Un spécificateur volatile indique au compilateur qu'un objet peut changer de valeur d'une manière non spécifiée par le langage, de sorte que des optimisations agressives doivent être évitées.
Le mot-clé volatile est un modificateur de type. Une variable déclarée avec lui indique qu'elle peut être modifiée par des facteurs inconnus du compilateur, tels que le système d'exploitation, le matériel ou d'autres threads. Lorsqu'il rencontre une variable déclarée avec ce mot-clé, le compilateur n'optimise plus le code qui accède à la variable, ce qui fournit un accès stable à des adresses spéciales. La syntaxe de déclaration est :int volatile vInt;Lorsque la valeur d'une variable déclarée avec volatile est demandée, le système relit toujours les données depuis la mémoire où elle réside, même si l'instruction précédente vient de lire des données à cet emplacement. De plus, les données lues sont immédiatement sauvegardées. Par exemple :
volatile int i=10; int a = i; ... // 其他代码,并未明确告诉编译器,对 i 进行过操作 int b = i;
volatile indique que i peut changer à tout moment. Chaque fois qu'il est utilisé, il doit être lu à l'adresse de i. Par conséquent, le code assembleur généré par le compilateur relira les données à l'adresse de i et les placera dans b. L'approche optimisée, cependant, est que, comme le compilateur constate que le code entre les deux lectures de i n'agit pas sur i, il place automatiquement les données précédemment lues dans b au lieu de les relire depuis i. Ainsi, si i est une variable de registre ou représente des données de port, des erreurs peuvent facilement se produire. Par conséquent, volatile peut garantir un accès stable aux adresses spéciales. Notez que dans VC 6, le mode debug général n'effectue pas d'optimisation de code, donc l'effet de ce mot-clé ne peut pas être vu. Ci-dessous, en insérant du code assembleur, nous testons l'impact de la présence ou de l'absence du mot-clé volatile sur le code final du programme. Saisissez le code suivant :
Exemple
Ensuite, exécutez le programme en mode Debug, et le résultat de sortie est le suivant :
i = 10 i = 32
Ensuite, exécutez le programme en mode Release, et le résultat de sortie est le suivant :
i = 10 i = 10
La sortie montre clairement qu'en mode Release, le compilateur a optimisé le code, et la deuxième fois, il n'a pas affiché la valeur correcte de i. Ci-dessous, ajoutons le mot-clé volatile à la déclaration de i et voyons ce qui change :
Exemple
Exécutez le programme respectivement dans les versions Debug et Release, et la sortie dans les deux est :
i = 10 i = 32
Cela montre que le mot-clé volatile a joué son rôle. En fait, ce n'est pas seulement l'approche « manipulation de la pile via l'assembleur en ligne » qui constitue des modifications de variables non reconnaissables par le compilateur. Plus couramment, lorsque plusieurs threads accèdent simultanément à des variables partagées et qu'un thread modifie la valeur d'une variable, comment la valeur modifiée peut-elle être rendue visible aux autres threads ? D'une manière générale, volatile est utilisé dans les cas suivants :
- 1) Les variables modifiées dans les routines de service d'interruption et utilisées pour la détection par d'autres programmes doivent être déclarées volatile ;
- 2) Les indicateurs partagés entre les tâches dans un environnement multitâche doivent être déclarés volatile ;
- 3) Les registres matériels mappés en mémoire ont généralement aussi besoin du qualificatif volatile, car chaque lecture ou écriture de ceux-ci peut avoir une signification différente ;
2. Pointeurs volatile
Semblable au modificateur const — const a les notions de pointeur vers const et de pointeur const — volatile a aussi les notions correspondantes :
Modifier l'objet pointé par le pointeur ; les données sont const ou volatile :
const char* cpch; volatile char* vpch;
Remarque : pour VC, cette fonctionnalité n'est sûre que si elle est implémentée dans VC 8 ou une version ultérieure.
La valeur du pointeur lui-même — une variable entière représentant une adresse — est const ou volatile :
char* const pchc; char* volatile pchv;
Remarque :
- (1) Vous pouvez affecter un int non volatile à un int volatile, mais vous ne pouvez pas affecter un objet non volatile à un objet volatile.
- (2) Outre les types de base, les types définis par l'utilisateur peuvent également être modifiés avec le type volatile.
- (3) En C++, une classe avec un identifiant volatile ne peut accéder qu'à un sous-ensemble de son interface, un sous-ensemble contrôlé par l'implémenteur de la classe. Les utilisateurs ne peuvent utiliser const_cast que pour obtenir un accès complet à l'interface du type. De plus, volatile, comme const, se propage d'une classe à ses membres.
3. volatile dans le multithreading
Certaines variables sont déclarées avec le mot-clé volatile. Lorsque deux threads doivent tous deux utiliser une certaine variable et que la valeur de cette variable sera modifiée, elle doit être déclarée avec volatile. Le rôle de ce mot-clé est d'empêcher le compilateur optimisant de charger la variable depuis la mémoire dans un registre du CPU. Si la variable est chargée dans un registre, alors les deux threads peuvent utiliser respectivement la variable en mémoire et la variable dans un registre, ce qui peut provoquer une exécution incorrecte du programme. volatile signifie demander au compilateur de toujours récupérer réellement la variable depuis la mémoire à chaque fois qu'il l'utilise, plutôt que d'utiliser la valeur déjà stockée dans le registre, comme suit :
volatile BOOL bStop = FALSE;
(1) Dans un thread :
while( !bStop ) { ... }
bStop = FALSE;
return;
(2) Dans un autre thread, pour terminer la boucle de thread ci-dessus :
bStop = TRUE; while( bStop ); //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。
Ce mot-clé est utilisé pour définir l'emplacement de stockage d'un objet en mémoire plutôt que dans un registre. Car pour les objets ordinaires, le compilateur peut placer une copie de ceux-ci dans un registre afin d'accélérer l'exécution des instructions, par exemple dans le code suivant :
...
int nMyCounter = 0;
for(; nMyCounter<100;nMyCounter++)
{
...
}
...
Dans ce segment de code, une copie de nMyCounter peut être stockée dans un registre (dans la boucle, les tests et les opérations sur nMyCounter sont toujours effectués sur la valeur de ce registre), mais il existe un autre segment de code qui effectue une opération comme celle-ci :nMyCounter -= 1;Dans cette opération, la modification de nMyCounter est effectuée sur le nMyCounter en mémoire, ce qui donne lieu à un phénomène : les modifications de nMyCounter ne sont pas synchronisées.
Adresse d'origine : https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html