Il existe une technique intrigante qui permet à votre compilateur de détecter d'éventuelles conditions de concurrence qui s'appuie fortement sur le mot clé volatile, vous pouvez en lire plus à l' adresse http://www.ddj.com/cpp/184403766 .
Neno Ganchev
Ceci est une belle ressource avec un exemple sur quand volatilepeut être utilisé efficacement, mis en place en termes assez simples. Lien: publications.gbdirect.co.uk/c_book/chapter8/…
Codeur optimisé
Je l'utilise pour le verrouillage sans code / verrouillage à double vérification
paulm
Pour moi, volatileplus utile que le friendmot-clé.
acegs
Réponses:
268
volatile est nécessaire si vous lisez à partir d'un emplacement en mémoire dans lequel, disons, un processus / périphérique / tout ce qui peut être complètement écrit.
J'avais l'habitude de travailler avec un ram à deux ports dans un système multiprocesseur en ligne droite C. Nous avons utilisé une valeur 16 bits gérée par le matériel comme sémaphore pour savoir quand l'autre gars avait terminé. Essentiellement, nous avons fait ceci:
void waitForSemaphore(){volatileuint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/while((*semPtr)!= IS_OK_FOR_ME_TO_PROCEED);}
Sans volatile, l'optimiseur voit la boucle comme inutile (le gars ne définit jamais la valeur! Il est fou, débarrassez-vous de ce code!) Et mon code continuerait sans avoir acquis le sémaphore, causant des problèmes plus tard.
Dans ce cas, que se passerait-il s'il uint16_t* volatile semPtrétait écrit à la place? Cela devrait marquer le pointeur comme volatile (au lieu de la valeur pointée), afin que les vérifications sur le pointeur lui-même, par exemple, semPtr == SOME_ADDRne soient pas optimisées. Cependant, cela implique également une valeur ponctuelle volatile. Non?
Zyl
@Zyl Non, ce n'est pas le cas. En pratique, ce que vous proposez est probablement ce qui va se passer. Mais théoriquement, on pourrait se retrouver avec un compilateur qui optimise l'accès aux valeurs car il a décidé qu'aucune de ces valeurs n'est jamais modifiée. Et si vous vouliez que volatile s'applique à la valeur et non au pointeur, vous seriez foutu. Encore une fois, peu probable, mais il vaut mieux se tromper en faisant les choses correctement, que de profiter du comportement qui se produit aujourd'hui.
iheanyi
1
@Doug T. Une meilleure explication est la suivante
machineaddict
3
@curiousguy, il n'a pas mal décidé. Il a effectué la déduction correcte sur la base des informations fournies. Si vous ne marquez pas quelque chose de volatile, le compilateur est libre de supposer qu'il n'est pas volatile . C'est ce que fait le compilateur lors de l'optimisation du code. S'il y a plus d'informations, à savoir que ces données sont en fait volatiles, c'est la responsabilité du programmeur de fournir ces informations. Ce que vous prétendez d'un compilateur de bogues, c'est vraiment une mauvaise programmation.
iheanyi
1
@curiousguy non, ce n'est pas parce que le mot-clé volatile apparaît une fois que tout devient soudainement volatil. J'ai donné un scénario où le compilateur fait la bonne chose et obtient un résultat contraire à ce que le programmeur attend à tort. Tout comme "l'analyse la plus vexante" n'est pas le signe de l'erreur du compilateur, ce n'est pas le cas ici non plus.
iheanyi
82
volatileest nécessaire lors du développement de systèmes intégrés ou de pilotes de périphériques, où vous devez lire ou écrire un périphérique matériel mappé en mémoire. Le contenu d'un registre de périphérique particulier peut changer à tout moment, vous avez donc besoin du volatilemot - clé pour vous assurer que ces accès ne sont pas optimisés par le compilateur.
Ceci n'est pas seulement valable pour les systèmes embarqués mais pour tous les développements de pilotes de périphériques.
Mladen Janković
La seule fois où j'en ai eu besoin sur un bus ISA 8 bits où vous lisez deux fois la même adresse - le compilateur a eu un bogue et l'a ignoré (début Zortech c ++)
Martin Beckett
La volatilité est très rarement suffisante pour contrôler des appareils externes. Sa sémantique est fausse pour le MMIO moderne: il faut rendre trop d'objets volatils et ça nuit à l'optimisation. Mais le MMIO moderne se comporte comme la mémoire normale jusqu'à ce qu'un indicateur soit défini, de sorte que la volatilité ne devrait pas être nécessaire. De nombreux pilotes n'utilisent jamais de volatile.
curiousguy
69
Certains processeurs ont des registres à virgule flottante qui ont plus de 64 bits de précision (par exemple, x86 32 bits sans SSE, voir le commentaire de Peter). De cette façon, si vous exécutez plusieurs opérations sur des nombres à double précision, vous obtenez en fait une réponse plus précise que si vous deviez tronquer chaque résultat intermédiaire à 64 bits.
C'est généralement génial, mais cela signifie que selon la façon dont le compilateur a affecté les registres et fait les optimisations, vous obtiendrez des résultats différents pour les mêmes opérations exactes sur les mêmes entrées. Si vous avez besoin de cohérence, vous pouvez forcer chaque opération à revenir en mémoire en utilisant le mot-clé volatile.
Il est également utile pour certains algorithmes qui n'ont aucun sens algébrique mais réduisent l'erreur en virgule flottante, comme la sommation de Kahan. Algébriquement, c'est un nop, donc il sera souvent mal optimisé à moins que certaines variables intermédiaires ne soient volatiles.
Lorsque vous calculez des dérivées numériques, cela est également utile, pour vous assurer que x + h - x == h vous définissez hh = x + h - x comme volatile afin qu'un delta approprié puisse être calculé.
Alexandre C.
5
+1, en effet dans mon expérience, il y avait un cas où les calculs à virgule flottante produisaient des résultats différents dans Debug et Release, donc les tests unitaires écrits pour une configuration échouaient pour une autre. Nous l'avons résolu en déclarant une variable à virgule flottante au volatile doublelieu de juste double, afin de nous assurer qu'elle est tronquée de la précision FPU à la précision 64 bits (RAM) avant de poursuivre les calculs. Les résultats étaient substantiellement différents en raison d'une exagération supplémentaire de l'erreur en virgule flottante.
Serge Rogatch
Votre définition de «moderne» est un peu décalée. Seul le code x86 32 bits qui évite SSE / SSE2 est affecté par cela, et il n'était pas "moderne" il y a 10 ans. MIPS / ARM / POWER ont tous des registres matériels 64 bits, tout comme x86 avec SSE2. Les implémentations C ++ x86-64 utilisent toujours SSE2, et les compilateurs ont des options comme l' g++ -mfpmath=sseutiliser également pour x86 32 bits. Vous pouvez utiliser gcc -ffloat-storepour forcer l'arrondi partout même lorsque vous utilisez x87, ou vous pouvez définir la précision x87 sur une mantisse 53 bits: randomascii.wordpress.com/2012/03/21/… .
Peter Cordes
Mais toujours une bonne réponse, pour le code-gen x87 obsolète, vous pouvez utiliser volatilepour forcer l'arrondi à quelques endroits spécifiques sans perdre les avantages partout.
Peter Cordes
1
Ou dois-je confondre inexact avec incohérent?
Chipster
49
Extrait d'un article "Volatile as a promise" de Dan Saks:
(...) un objet volatil est un objet dont la valeur peut changer spontanément. Autrement dit, lorsque vous déclarez un objet volatil, vous dites au compilateur que l'objet peut changer d'état même si aucune instruction du programme ne semble le changer. "
Voici des liens vers trois de ses articles concernant le volatilemot - clé:
Vous DEVEZ utiliser volatile lors de l'implémentation de structures de données sans verrouillage. Sinon, le compilateur est libre d'optimiser l'accès à la variable, ce qui changera la sémantique.
En d'autres termes, volatile indique au compilateur que l'accès à cette variable doit correspondre à une opération de lecture / écriture de la mémoire physique.
Par exemple, voici comment InterlockedIncrement est déclaré dans l'API Win32:
LONG __cdecl InterlockedIncrement(
__inout LONG volatile*Addend);
Vous n'avez absolument PAS besoin de déclarer une variable volatile pour pouvoir utiliser InterlockedIncrement.
curiousguy
Cette réponse est obsolète maintenant que C ++ 11 fournit std::atomic<LONG>afin que vous puissiez écrire du code sans verrou de manière plus sûre sans problème d'avoir des charges pures / des magasins purs optimisés, réorganisés, ou autre chose.
Peter Cordes
10
Une grande application sur laquelle je travaillais au début des années 90 contenait une gestion des exceptions basée sur C utilisant setjmp et longjmp. Le mot-clé volatile était nécessaire sur les variables dont les valeurs devaient être conservées dans le bloc de code qui servait de clause "catch", de peur que ces variables ne soient stockées dans des registres et effacées par le longjmp.
Dans la norme C, l'un des endroits à utiliser volatileest avec un gestionnaire de signal. En fait, dans la norme C, tout ce que vous pouvez faire en toute sécurité dans un gestionnaire de signaux est de modifier une volatile sig_atomic_tvariable ou de quitter rapidement. En effet, AFAIK, c'est le seul endroit de la norme C dont l'utilisation volatileest requise pour éviter un comportement indéfini.
ISO / IEC 9899: 2011 §7.14.1.1 La signalfonction
¶5 Si le signal se produit autrement qu'à la suite de l'appel de la fonction abortou raise, le comportement n'est pas défini si le gestionnaire de signal se réfère à tout objet avec une durée de stockage statique ou de thread qui n'est pas un objet atomique sans verrouillage autrement qu'en assignant une valeur à un objet déclaré en tant que volatile sig_atomic_t, ou le gestionnaire de signaux appelle toute fonction de la bibliothèque standard autre que la abortfonction, la _Exitfonction, la
quick_exitfonction ou la signalfonction avec le premier argument égal au numéro de signal correspondant au signal qui a provoqué l'invocation du gestionnaire. De plus, si un tel appel à la signalfonction aboutit à un retour SIG_ERR, la valeur de errnoest indéterminée. 252)
252) Si un signal est généré par un gestionnaire de signal asynchrone, le comportement n'est pas défini.
Cela signifie que dans la norme C, vous pouvez écrire:
POSIX est beaucoup plus indulgent sur ce que vous pouvez faire dans un gestionnaire de signaux, mais il y a encore des limitations (et l'une des limitations est que la bibliothèque d'E / S standard - printf()et al - ne peut pas être utilisée en toute sécurité).
En développant pour un embarqué, j'ai une boucle qui vérifie une variable qui peut être modifiée dans un gestionnaire d'interruption. Sans "volatile", la boucle devient un noop - pour autant que le compilateur puisse le dire, la variable ne change jamais, donc elle optimise la vérification.
La même chose s'appliquerait à une variable qui peut être modifiée dans un thread différent dans un environnement plus traditionnel, mais là, nous faisons souvent des appels de synchronisation, donc le compilateur n'est pas aussi libre d'optimisation.
Je l'ai utilisé dans les versions de débogage lorsque le compilateur insiste sur l'optimisation d'une variable que je veux pouvoir voir lorsque je parcours le code.
En plus de l'utiliser comme prévu, volatile est utilisé dans la métaprogrammation (modèle). Il peut être utilisé pour éviter une surcharge accidentelle, car l'attribut volatile (comme const) participe à la résolution de la surcharge.
C'est légal; les deux surcharges sont potentiellement appelables et font presque la même chose. Le plâtre dans la volatilesurcharge est légal car nous savons que la barre ne passera pas de Ttoute façon non volatile . La volatileversion est strictement pire, cependant, donc jamais choisie dans la résolution de surcharge si le non-volatilef est disponible.
Notez que le code ne dépend jamais réellement de l' volatileaccès à la mémoire.
Pourriez-vous, s'il vous plaît, développer cela avec un exemple? Cela m'aiderait vraiment à mieux comprendre. Merci!
batbrat
" Le cast dans la surcharge volatile " Un cast est une conversion explicite. C'est une construction SYNTAX. Beaucoup de gens font cette confusion (même les auteurs standard).
curiousguy
6
vous devez l'utiliser pour implémenter des verrous tournants ainsi que certaines (toutes?) structures de données sans verrouillage
l'utiliser avec des opérations / instructions atomiques
m'a aidé une fois à surmonter le bogue du compilateur (code généré à tort lors de l'optimisation)
Il vaut mieux utiliser une bibliothèque, les éléments intrinsèques du compilateur ou le code d'assemblage en ligne. La volatilité n'est pas fiable.
Zan Lynx
1
1 et 2 utilisent tous deux des opérations atomiques, mais volatile ne fournit pas de sémantique atomique et les implémentations atomiques spécifiques à la plate-forme remplaceront le besoin d'utiliser volatile, donc pour 1 et 2, je ne suis pas d'accord, vous n'avez PAS besoin de volatile pour celles-ci.
Qui dit quoi que ce soit sur la sémantique atomique volatile? J'ai dit que vous devez UTILISER des opérations atomiques volatiles AVEC et si vous ne pensez pas que ce soit vrai, regardez les déclarations d'opérations imbriquées de l'API win32 (ce gars l'a également expliqué dans sa réponse)
Mladen Janković
4
le volatile mot-clé est destiné à empêcher le compilateur d'appliquer des optimisations sur des objets qui peuvent changer d'une manière qui ne peut pas être déterminée par le compilateur.
Les objets déclarés comme volatilesont omis de l'optimisation car leurs valeurs peuvent être modifiées à tout moment par du code hors de la portée du code actuel. Le système lit toujours la valeur actuelle d'un volatileobjet à partir de l'emplacement de mémoire plutôt que de conserver sa valeur dans le registre temporaire au point où elle est demandée, même si une instruction précédente demandait une valeur du même objet.
Considérez les cas suivants
1) Variables globales modifiées par une routine de service d'interruption en dehors de la portée.
2) Variables globales dans une application multi-thread.
Si nous n'utilisons pas de qualificatif volatil, les problèmes suivants peuvent survenir
1) Le code peut ne pas fonctionner comme prévu lorsque l'optimisation est activée.
2) Le code peut ne pas fonctionner comme prévu lorsque les interruptions sont activées et utilisées.
Le lien que vous avez publié est extrêmement obsolète et ne reflète pas les meilleures pratiques actuelles.
Tim Seguine
2
Outre le fait que le mot clé volatile est utilisé pour dire au compilateur de ne pas optimiser l'accès à certaines variables (qui peuvent être modifiées par un thread ou une routine d'interruption), il peut également être utilisé pour supprimer certains bogues du compilateur - OUI, il peut être ---.
Par exemple, j'ai travaillé sur une plate-forme embarquée où le compilateur faisait de fausses hypothèses concernant la valeur d'une variable. Si le code n'était pas optimisé, le programme fonctionnerait correctement. Avec des optimisations (qui étaient vraiment nécessaires car c'était une routine critique), le code ne fonctionnerait pas correctement. La seule solution (mais pas très correcte) était de déclarer la variable «défectueuse» comme volatile.
C'est une hypothèse erronée l'idée que le compilateur n'optimise pas l'accès aux volatiles. La norme ne sait rien des optimisations. Le compilateur est tenu de respecter ce que dicte la norme, mais il est libre de faire toutes les optimisations qui n'interfèrent pas avec le comportement normal.
Terminus le
3
D'après mon expérience, 99,9% de tous les "bugs" d'optimisation dans gcc arm sont des erreurs de la part du programmeur. Aucune idée si cela s'applique à cette réponse. Juste un coup de gueule sur le sujet général
odinthenerd
@Terminus " C'est une hypothèse erronée l'idée que le compilateur n'optimise pas l'accès aux volatiles " Source?
curiousguy
2
Votre programme semble fonctionner même sans volatilemot-clé? C'est peut-être la raison:
Comme mentionné précédemment, le volatilemot clé aide dans des cas comme
volatileint* p =...;// point to some memorywhile(*p!=0){}// loop until the memory becomes zero
Mais il semble qu'il n'y ait presque aucun effet une fois qu'une fonction externe ou non en ligne est appelée. Par exemple:
while(*p!=0){ g();}
Ensuite, avec ou sans volatilepresque le même résultat est généré.
Tant que g () peut être complètement intégré, le compilateur peut voir tout ce qui se passe et peut donc optimiser. Mais lorsque le programme appelle un endroit où le compilateur ne peut pas voir ce qui se passe, il n'est plus sûr pour le compilateur de faire des hypothèses. Par conséquent, le compilateur générera du code qui lit toujours directement depuis la mémoire.
Mais méfiez-vous du jour, lorsque votre fonction g () devient inline (soit à cause de changements explicites, soit à cause de l'intelligence du compilateur / éditeur de liens), alors votre code risque de casser si vous oubliez le volatilemot - clé!
Par conséquent, je recommande d'ajouter le volatilemot-clé même si votre programme semble fonctionner sans. Il rend l'intention plus claire et plus solide en ce qui concerne les changements futurs.
Notez qu'une fonction peut avoir son code en ligne tout en générant toujours une référence (résolue au moment du lien) à la fonction de contour; ce sera le cas d'une fonction récursive partiellement en ligne. Une fonction peut également avoir sa sémantique "inline" par le compilateur, c'est-à-dire que le compilateur suppose que les effets secondaires et le résultat sont dans les effets secondaires possibles et les résultats possibles en fonction de son code source, sans pour autant l'inclure. Ceci est basé sur la "règle de définition unique efficace" qui stipule que toutes les définitions d'une entité doivent être effectivement équivalentes (sinon exactement identiques).
curiousguy
Éviter de manière portative l'incrustation d'un appel (ou "incrustation" de sa sémantique) par une fonction dont le corps est visible par le compilateur (même au moment de la liaison avec l'optimisation globale) est possible en utilisant un volatilepointeur de fonction qualifié:void (* volatile fun_ptr)() = fun; fun_ptr();
curiousguy
2
Dans les premiers jours de C, les compilateurs interprétaient toutes les actions qui lisent et écrivent des valeurs comme des opérations de mémoire, à effectuer dans la même séquence que les lectures et les écritures apparaissaient dans le code. L'efficacité pourrait être considérablement améliorée dans de nombreux cas si les compilateurs disposaient d'une certaine liberté pour réorganiser et consolider les opérations, mais cela posait un problème. Même les opérations étaient souvent spécifiées dans un certain ordre simplement parce qu'il était nécessaire de les spécifier dans un certain ordre, et donc le programmeur a choisi l'une des nombreuses alternatives tout aussi bonnes, ce n'était pas toujours le cas. Parfois, il serait important que certaines opérations se produisent dans une séquence particulière.
Les détails précis du séquençage qui sont importants varient en fonction de la plate-forme cible et du domaine d'application. Plutôt que de fournir un contrôle particulièrement détaillé, la norme a opté pour un modèle simple: si une séquence d'accès est effectuée avec des valeurs non qualifiées volatile, un compilateur peut les réorganiser et les consolider comme bon lui semble. Si une action est effectuée avec une volatilevaleur l qualifiée, une implémentation de qualité doit offrir toutes les garanties de commande supplémentaires qui pourraient être requises par le code ciblant sa plate-forme et son champ d'application prévus, sans avoir à utiliser une syntaxe non standard.
Malheureusement, plutôt que d'identifier les garanties dont les programmeurs auraient besoin, de nombreux compilateurs ont plutôt opté pour offrir les garanties minimales nues imposées par la norme. Cela rend volatilebeaucoup moins utile qu'elle ne devrait l'être. Sur gcc ou clang, par exemple, un programmeur devant implémenter un "mutex de transfert" de base [un où une tâche qui a acquis et publié un mutex ne le fera plus tant que l'autre tâche ne l'a pas fait] doit en faire un de quatre choses:
Mettez l'acquisition et la libération du mutex dans une fonction que le compilateur ne peut pas incorporer et à laquelle il ne peut pas appliquer Whole Program Optimization.
Qualifiez tous les objets gardés par le mutex comme - volatilequelque chose qui ne devrait pas être nécessaire si tous les accès se produisent après l'acquisition du mutex et avant de le libérer.
Utilisez le niveau d'optimisation 0 pour forcer le compilateur à générer du code comme si tous les objets non qualifiés le registersont volatile.
Utilisez des directives spécifiques à gcc.
En revanche, lorsque vous utilisez un compilateur de meilleure qualité qui convient mieux à la programmation de systèmes, comme icc, on aurait une autre option:
Assurez-vous qu'une volatileécriture qualifiée est effectuée partout où une acquisition ou une libération est nécessaire.
L'acquisition d'un "mutex de transfert" de base nécessite une volatilelecture (pour voir s'il est prêt), et ne devrait pas non plus nécessiter une volatileécriture (l'autre côté n'essaiera pas de le réacquérir jusqu'à ce qu'il soit rendu), mais effectuer une volatileécriture sans signification est toujours meilleur que n'importe quelle option disponible sous gcc ou clang.
Une utilisation que je dois vous rappeler est que, dans la fonction de gestion du signal, si vous souhaitez accéder / modifier une variable globale (par exemple, la marquer comme exit = true), vous devez déclarer cette variable comme «volatile».
Toutes les réponses sont excellentes. Mais en plus de cela, je voudrais partager un exemple.
Voici un petit programme cpp:
#include<iostream>int x;int main(){char buf[50];
x =8;if(x ==8)
printf("x is 8\n");else
sprintf(buf,"x is not 8\n");
x=1000;while(x >5)
x--;return0;}
Maintenant, permet de générer l'assembly du code ci-dessus (et je ne collerai que les parties de l'assembly pertinentes ici):
La commande pour générer l'assembly:
g++-S -O3 -c -fverbose-asm-Wa,-adhln assembly.cpp
Et l'assemblage:
main:.LFB1594:
subq $40,%rsp #,.seh_stackalloc 40.seh_endprologue
# assembly.cpp:5: int main(){
call __main ## assembly.cpp:10: printf("x is 8\n");
leaq .LC0(%rip),%rcx #,# assembly.cpp:7: x = 8;
movl $8, x(%rip)#, x# assembly.cpp:10: printf("x is 8\n");
call _ZL6printfPKcz.constprop.0## assembly.cpp:18: }
xorl %eax,%eax #
movl $5, x(%rip)#, x
addq $40,%rsp #,
ret
.seh_endproc
.p2align 4,,15.def _GLOBAL__sub_I_x;.scl 3;.type 32;.endef
.seh_proc _GLOBAL__sub_I_x
Vous pouvez voir dans l'assembly que le code d'assembly n'a pas été généré sprintfcar le compilateur a supposé que xcela ne changera pas en dehors du programme. Et c'est le cas avec la whileboucle. whileLa boucle a été complètement supprimée en raison de l'optimisation car le compilateur l'a vue comme un code inutile et donc directement affectée 5à x(voirmovl $5, x(%rip) ).
Le problème se produit lorsque que se passe-t-il si un processus / matériel externe change la valeur de xquelque part entre x = 8;et if(x == 8). Nous nous attendions à ce que le elsebloc fonctionne, mais malheureusement, le compilateur a supprimé cette partie.
Maintenant, afin de résoudre ce problème, en assembly.cpp, changeons int x;de volatile int x;et voir rapidement le code assembleur généré:
Ici vous pouvez voir que les codes d'assemblage pour sprintf, printfet la whileboucle ont été générés. L'avantage est que si la xvariable est modifiée par un programme ou un matériel externe, une sprintfpartie du code sera exécutée. Et de même, la whileboucle peut être utilisée pour une attente occupée maintenant.
D'autres réponses mentionnent déjà d'éviter une certaine optimisation afin de:
utiliser des registres mappés en mémoire (ou "MMIO")
écrire des pilotes de périphérique
permettre un débogage plus facile des programmes
rendre les calculs en virgule flottante plus déterministes
La volatilité est essentielle chaque fois que vous avez besoin qu'une valeur semble provenir de l'extérieur et soit imprévisible et évite les optimisations du compilateur basées sur une valeur connue, et lorsqu'un résultat n'est pas réellement utilisé mais que vous avez besoin qu'il soit calculé, ou qu'il soit utilisé mais vous voulez le calculer plusieurs fois pour une référence, et vous avez besoin des calculs pour commencer et terminer à des points précis.
Une lecture volatile est comme une opération d'entrée (comme scanfou une utilisation de cin): la valeur semble provenir de l'extérieur du programme, donc tout calcul qui dépend de la valeur doit commencer après .
Une écriture volatile est comme une opération de sortie (comme printfou une utilisation de cout): la valeur semble être communiquée en dehors du programme, donc si la valeur dépend d'un calcul, elle doit être terminée avant .
Ainsi, une paire de lecture / écriture volatile peut être utilisée pour apprivoiser les repères et donner un sens à la mesure du temps .
Sans volatilité, votre calcul pourrait être démarré par le compilateur auparavant, car rien n'empêcherait le réordonnancement des calculs avec des fonctions telles que la mesure du temps .
volatile
peut être utilisé efficacement, mis en place en termes assez simples. Lien: publications.gbdirect.co.uk/c_book/chapter8/…volatile
plus utile que lefriend
mot-clé.Réponses:
volatile
est nécessaire si vous lisez à partir d'un emplacement en mémoire dans lequel, disons, un processus / périphérique / tout ce qui peut être complètement écrit.J'avais l'habitude de travailler avec un ram à deux ports dans un système multiprocesseur en ligne droite C. Nous avons utilisé une valeur 16 bits gérée par le matériel comme sémaphore pour savoir quand l'autre gars avait terminé. Essentiellement, nous avons fait ceci:
Sans
volatile
, l'optimiseur voit la boucle comme inutile (le gars ne définit jamais la valeur! Il est fou, débarrassez-vous de ce code!) Et mon code continuerait sans avoir acquis le sémaphore, causant des problèmes plus tard.la source
uint16_t* volatile semPtr
était écrit à la place? Cela devrait marquer le pointeur comme volatile (au lieu de la valeur pointée), afin que les vérifications sur le pointeur lui-même, par exemple,semPtr == SOME_ADDR
ne soient pas optimisées. Cependant, cela implique également une valeur ponctuelle volatile. Non?volatile
est nécessaire lors du développement de systèmes intégrés ou de pilotes de périphériques, où vous devez lire ou écrire un périphérique matériel mappé en mémoire. Le contenu d'un registre de périphérique particulier peut changer à tout moment, vous avez donc besoin duvolatile
mot - clé pour vous assurer que ces accès ne sont pas optimisés par le compilateur.la source
Certains processeurs ont des registres à virgule flottante qui ont plus de 64 bits de précision (par exemple, x86 32 bits sans SSE, voir le commentaire de Peter). De cette façon, si vous exécutez plusieurs opérations sur des nombres à double précision, vous obtenez en fait une réponse plus précise que si vous deviez tronquer chaque résultat intermédiaire à 64 bits.
C'est généralement génial, mais cela signifie que selon la façon dont le compilateur a affecté les registres et fait les optimisations, vous obtiendrez des résultats différents pour les mêmes opérations exactes sur les mêmes entrées. Si vous avez besoin de cohérence, vous pouvez forcer chaque opération à revenir en mémoire en utilisant le mot-clé volatile.
Il est également utile pour certains algorithmes qui n'ont aucun sens algébrique mais réduisent l'erreur en virgule flottante, comme la sommation de Kahan. Algébriquement, c'est un nop, donc il sera souvent mal optimisé à moins que certaines variables intermédiaires ne soient volatiles.
la source
volatile double
lieu de justedouble
, afin de nous assurer qu'elle est tronquée de la précision FPU à la précision 64 bits (RAM) avant de poursuivre les calculs. Les résultats étaient substantiellement différents en raison d'une exagération supplémentaire de l'erreur en virgule flottante.g++ -mfpmath=sse
utiliser également pour x86 32 bits. Vous pouvez utilisergcc -ffloat-store
pour forcer l'arrondi partout même lorsque vous utilisez x87, ou vous pouvez définir la précision x87 sur une mantisse 53 bits: randomascii.wordpress.com/2012/03/21/… .volatile
pour forcer l'arrondi à quelques endroits spécifiques sans perdre les avantages partout.Extrait d'un article "Volatile as a promise" de Dan Saks:
Voici des liens vers trois de ses articles concernant le
volatile
mot - clé:la source
Vous DEVEZ utiliser volatile lors de l'implémentation de structures de données sans verrouillage. Sinon, le compilateur est libre d'optimiser l'accès à la variable, ce qui changera la sémantique.
En d'autres termes, volatile indique au compilateur que l'accès à cette variable doit correspondre à une opération de lecture / écriture de la mémoire physique.
Par exemple, voici comment InterlockedIncrement est déclaré dans l'API Win32:
la source
std::atomic<LONG>
afin que vous puissiez écrire du code sans verrou de manière plus sûre sans problème d'avoir des charges pures / des magasins purs optimisés, réorganisés, ou autre chose.Une grande application sur laquelle je travaillais au début des années 90 contenait une gestion des exceptions basée sur C utilisant setjmp et longjmp. Le mot-clé volatile était nécessaire sur les variables dont les valeurs devaient être conservées dans le bloc de code qui servait de clause "catch", de peur que ces variables ne soient stockées dans des registres et effacées par le longjmp.
la source
Dans la norme C, l'un des endroits à utiliser
volatile
est avec un gestionnaire de signal. En fait, dans la norme C, tout ce que vous pouvez faire en toute sécurité dans un gestionnaire de signaux est de modifier unevolatile sig_atomic_t
variable ou de quitter rapidement. En effet, AFAIK, c'est le seul endroit de la norme C dont l'utilisationvolatile
est requise pour éviter un comportement indéfini.Cela signifie que dans la norme C, vous pouvez écrire:
et pas grand chose d'autre.
POSIX est beaucoup plus indulgent sur ce que vous pouvez faire dans un gestionnaire de signaux, mais il y a encore des limitations (et l'une des limitations est que la bibliothèque d'E / S standard -
printf()
et al - ne peut pas être utilisée en toute sécurité).la source
En développant pour un embarqué, j'ai une boucle qui vérifie une variable qui peut être modifiée dans un gestionnaire d'interruption. Sans "volatile", la boucle devient un noop - pour autant que le compilateur puisse le dire, la variable ne change jamais, donc elle optimise la vérification.
La même chose s'appliquerait à une variable qui peut être modifiée dans un thread différent dans un environnement plus traditionnel, mais là, nous faisons souvent des appels de synchronisation, donc le compilateur n'est pas aussi libre d'optimisation.
la source
Je l'ai utilisé dans les versions de débogage lorsque le compilateur insiste sur l'optimisation d'une variable que je veux pouvoir voir lorsque je parcours le code.
la source
En plus de l'utiliser comme prévu, volatile est utilisé dans la métaprogrammation (modèle). Il peut être utilisé pour éviter une surcharge accidentelle, car l'attribut volatile (comme const) participe à la résolution de la surcharge.
C'est légal; les deux surcharges sont potentiellement appelables et font presque la même chose. Le plâtre dans la
volatile
surcharge est légal car nous savons que la barre ne passera pas deT
toute façon non volatile . Lavolatile
version est strictement pire, cependant, donc jamais choisie dans la résolution de surcharge si le non-volatilef
est disponible.Notez que le code ne dépend jamais réellement de l'
volatile
accès à la mémoire.la source
la source
le
volatile
mot-clé est destiné à empêcher le compilateur d'appliquer des optimisations sur des objets qui peuvent changer d'une manière qui ne peut pas être déterminée par le compilateur.Les objets déclarés comme
volatile
sont omis de l'optimisation car leurs valeurs peuvent être modifiées à tout moment par du code hors de la portée du code actuel. Le système lit toujours la valeur actuelle d'unvolatile
objet à partir de l'emplacement de mémoire plutôt que de conserver sa valeur dans le registre temporaire au point où elle est demandée, même si une instruction précédente demandait une valeur du même objet.Considérez les cas suivants
1) Variables globales modifiées par une routine de service d'interruption en dehors de la portée.
2) Variables globales dans une application multi-thread.
Si nous n'utilisons pas de qualificatif volatil, les problèmes suivants peuvent survenir
1) Le code peut ne pas fonctionner comme prévu lorsque l'optimisation est activée.
2) Le code peut ne pas fonctionner comme prévu lorsque les interruptions sont activées et utilisées.
Volatile: le meilleur ami d'un programmeur
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
la source
Outre le fait que le mot clé volatile est utilisé pour dire au compilateur de ne pas optimiser l'accès à certaines variables (qui peuvent être modifiées par un thread ou une routine d'interruption), il peut également être utilisé pour supprimer certains bogues du compilateur - OUI, il peut être ---.
Par exemple, j'ai travaillé sur une plate-forme embarquée où le compilateur faisait de fausses hypothèses concernant la valeur d'une variable. Si le code n'était pas optimisé, le programme fonctionnerait correctement. Avec des optimisations (qui étaient vraiment nécessaires car c'était une routine critique), le code ne fonctionnerait pas correctement. La seule solution (mais pas très correcte) était de déclarer la variable «défectueuse» comme volatile.
la source
Votre programme semble fonctionner même sans
volatile
mot-clé? C'est peut-être la raison:Comme mentionné précédemment, le
volatile
mot clé aide dans des cas commeMais il semble qu'il n'y ait presque aucun effet une fois qu'une fonction externe ou non en ligne est appelée. Par exemple:
Ensuite, avec ou sans
volatile
presque le même résultat est généré.Tant que g () peut être complètement intégré, le compilateur peut voir tout ce qui se passe et peut donc optimiser. Mais lorsque le programme appelle un endroit où le compilateur ne peut pas voir ce qui se passe, il n'est plus sûr pour le compilateur de faire des hypothèses. Par conséquent, le compilateur générera du code qui lit toujours directement depuis la mémoire.
Mais méfiez-vous du jour, lorsque votre fonction g () devient inline (soit à cause de changements explicites, soit à cause de l'intelligence du compilateur / éditeur de liens), alors votre code risque de casser si vous oubliez le
volatile
mot - clé!Par conséquent, je recommande d'ajouter le
volatile
mot-clé même si votre programme semble fonctionner sans. Il rend l'intention plus claire et plus solide en ce qui concerne les changements futurs.la source
volatile
pointeur de fonction qualifié:void (* volatile fun_ptr)() = fun; fun_ptr();
Dans les premiers jours de C, les compilateurs interprétaient toutes les actions qui lisent et écrivent des valeurs comme des opérations de mémoire, à effectuer dans la même séquence que les lectures et les écritures apparaissaient dans le code. L'efficacité pourrait être considérablement améliorée dans de nombreux cas si les compilateurs disposaient d'une certaine liberté pour réorganiser et consolider les opérations, mais cela posait un problème. Même les opérations étaient souvent spécifiées dans un certain ordre simplement parce qu'il était nécessaire de les spécifier dans un certain ordre, et donc le programmeur a choisi l'une des nombreuses alternatives tout aussi bonnes, ce n'était pas toujours le cas. Parfois, il serait important que certaines opérations se produisent dans une séquence particulière.
Les détails précis du séquençage qui sont importants varient en fonction de la plate-forme cible et du domaine d'application. Plutôt que de fournir un contrôle particulièrement détaillé, la norme a opté pour un modèle simple: si une séquence d'accès est effectuée avec des valeurs non qualifiées
volatile
, un compilateur peut les réorganiser et les consolider comme bon lui semble. Si une action est effectuée avec unevolatile
valeur l qualifiée, une implémentation de qualité doit offrir toutes les garanties de commande supplémentaires qui pourraient être requises par le code ciblant sa plate-forme et son champ d'application prévus, sans avoir à utiliser une syntaxe non standard.Malheureusement, plutôt que d'identifier les garanties dont les programmeurs auraient besoin, de nombreux compilateurs ont plutôt opté pour offrir les garanties minimales nues imposées par la norme. Cela rend
volatile
beaucoup moins utile qu'elle ne devrait l'être. Sur gcc ou clang, par exemple, un programmeur devant implémenter un "mutex de transfert" de base [un où une tâche qui a acquis et publié un mutex ne le fera plus tant que l'autre tâche ne l'a pas fait] doit en faire un de quatre choses:Mettez l'acquisition et la libération du mutex dans une fonction que le compilateur ne peut pas incorporer et à laquelle il ne peut pas appliquer Whole Program Optimization.
Qualifiez tous les objets gardés par le mutex comme -
volatile
quelque chose qui ne devrait pas être nécessaire si tous les accès se produisent après l'acquisition du mutex et avant de le libérer.Utilisez le niveau d'optimisation 0 pour forcer le compilateur à générer du code comme si tous les objets non qualifiés le
register
sontvolatile
.Utilisez des directives spécifiques à gcc.
En revanche, lorsque vous utilisez un compilateur de meilleure qualité qui convient mieux à la programmation de systèmes, comme icc, on aurait une autre option:
volatile
écriture qualifiée est effectuée partout où une acquisition ou une libération est nécessaire.L'acquisition d'un "mutex de transfert" de base nécessite une
volatile
lecture (pour voir s'il est prêt), et ne devrait pas non plus nécessiter unevolatile
écriture (l'autre côté n'essaiera pas de le réacquérir jusqu'à ce qu'il soit rendu), mais effectuer unevolatile
écriture sans signification est toujours meilleur que n'importe quelle option disponible sous gcc ou clang.la source
Une utilisation que je dois vous rappeler est que, dans la fonction de gestion du signal, si vous souhaitez accéder / modifier une variable globale (par exemple, la marquer comme exit = true), vous devez déclarer cette variable comme «volatile».
la source
Toutes les réponses sont excellentes. Mais en plus de cela, je voudrais partager un exemple.
Voici un petit programme cpp:
Maintenant, permet de générer l'assembly du code ci-dessus (et je ne collerai que les parties de l'assembly pertinentes ici):
La commande pour générer l'assembly:
Et l'assemblage:
Vous pouvez voir dans l'assembly que le code d'assembly n'a pas été généré
sprintf
car le compilateur a supposé quex
cela ne changera pas en dehors du programme. Et c'est le cas avec lawhile
boucle.while
La boucle a été complètement supprimée en raison de l'optimisation car le compilateur l'a vue comme un code inutile et donc directement affectée5
àx
(voirmovl $5, x(%rip)
).Le problème se produit lorsque que se passe-t-il si un processus / matériel externe change la valeur de
x
quelque part entrex = 8;
etif(x == 8)
. Nous nous attendions à ce que leelse
bloc fonctionne, mais malheureusement, le compilateur a supprimé cette partie.Maintenant, afin de résoudre ce problème, en
assembly.cpp
, changeonsint x;
devolatile int x;
et voir rapidement le code assembleur généré:Ici vous pouvez voir que les codes d'assemblage pour
sprintf
,printf
et lawhile
boucle ont été générés. L'avantage est que si lax
variable est modifiée par un programme ou un matériel externe, unesprintf
partie du code sera exécutée. Et de même, lawhile
boucle peut être utilisée pour une attente occupée maintenant.la source
D'autres réponses mentionnent déjà d'éviter une certaine optimisation afin de:
La volatilité est essentielle chaque fois que vous avez besoin qu'une valeur semble provenir de l'extérieur et soit imprévisible et évite les optimisations du compilateur basées sur une valeur connue, et lorsqu'un résultat n'est pas réellement utilisé mais que vous avez besoin qu'il soit calculé, ou qu'il soit utilisé mais vous voulez le calculer plusieurs fois pour une référence, et vous avez besoin des calculs pour commencer et terminer à des points précis.
Une lecture volatile est comme une opération d'entrée (comme
scanf
ou une utilisation decin
): la valeur semble provenir de l'extérieur du programme, donc tout calcul qui dépend de la valeur doit commencer après .Une écriture volatile est comme une opération de sortie (comme
printf
ou une utilisation decout
): la valeur semble être communiquée en dehors du programme, donc si la valeur dépend d'un calcul, elle doit être terminée avant .Ainsi, une paire de lecture / écriture volatile peut être utilisée pour apprivoiser les repères et donner un sens à la mesure du temps .
Sans volatilité, votre calcul pourrait être démarré par le compilateur auparavant, car rien n'empêcherait le réordonnancement des calculs avec des fonctions telles que la mesure du temps .
la source