Pourquoi la volatilité existe-t-elle?

222

Que fait le volatilemot-clé? En C ++ quel problème résout-il?

Dans mon cas, je n'en ai jamais sciemment eu besoin.

theschmitzer
la source
Voici une discussion intéressante sur la volatilité en ce qui concerne le modèle Singleton: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
chessguy
3
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()
{
   volatile uint16_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.

Doug T.
la source
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.

ChrisN
la source
9
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.

tfinniga
la source
5
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é:

MikeZ
la source
23

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
);
Frederik Slijkerman
la source
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.


la source
10

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:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

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é).

Jonathan Leffler
la source
7

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
7

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.

échancrure
la source
7

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.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

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.

MSalters
la source
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
  1. vous devez l'utiliser pour implémenter des verrous tournants ainsi que certaines (toutes?) structures de données sans verrouillage
  2. l'utiliser avec des opérations / instructions atomiques
  3. m'a aidé une fois à surmonter le bogue du compilateur (code généré à tort lors de l'optimisation)
Mladen Janković
la source
5
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.

Volatile: le meilleur ami d'un programmeur

https://en.wikipedia.org/wiki/Volatile_(computer_programming)

roottraveller
la source
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.

INS
la source
3
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

volatile int* p = ...;  // point to some memory
while( *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.

Joachim
la source
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:

  1. 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.

  2. 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.

  3. 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.

  4. 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:

  1. 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.

supercat
la source
1

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».

bugs king
la source
1

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--;
    return 0;
}

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é:

main:
.LFB1594:
    subq    $104, %rsp   #,
    .seh_stackalloc 104
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:9:     if(x == 8)
    movl    x(%rip), %eax    # x, x.1_1
 # assembly.cpp:9:     if(x == 8)
    cmpl    $8, %eax     #, x.1_1
    je  .L11     #,
 # assembly.cpp:12:         sprintf(buf, "x is not 8\n");
    leaq    32(%rsp), %rcx   #, tmp93
    leaq    .LC0(%rip), %rdx     #,
    call    _ZL7sprintfPcPKcz.constprop.0    #
.L7:
 # assembly.cpp:14:     x=1000;
    movl    $1000, x(%rip)   #, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_15
    cmpl    $5, %eax     #, x.3_15
    jle .L8  #,
    .p2align 4,,10
.L9:
 # assembly.cpp:16:         x--;
    movl    x(%rip), %eax    # x, x.4_3
    subl    $1, %eax     #, _4
    movl    %eax, x(%rip)    # _4, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_2
    cmpl    $5, %eax     #, x.3_2
    jg  .L9  #,
.L8:
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    addq    $104, %rsp   #,
    ret 
.L11:
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC1(%rip), %rcx     #,
    call    _ZL6printfPKcz.constprop.1   #
    jmp .L7  #
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

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.

Rohit
la source
0

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 .

curiousguy
la source