Was bedeutet es, eine volatile Variable zu deklarieren?

9

Viele Low-Level-Programme verwenden das flüchtige Schlüsselwort für Typen für die Speicherzuordnung und dergleichen, aber ich bin irgendwie verwirrt darüber, was es WIRKLICH im Hintergrund tut. Mit anderen Worten, was bedeutet es, wenn der Compiler die Speicheradresse nicht "wegoptimiert"?

Vikaton
quelle
1
Wenn Sie Ihr Alter aus einer volatileVariablen herauslesen und es 5
anzeigt
@ 5gon12eder, ich verstehe, dass flüchtig bedeutet, dass sich etwas schnell und einfach ändern kann, aber wie funktioniert das? : S
Vikaton
Abhängig von Ihren Kompilierungsflags wird möglicherweise auch eine nichtflüchtige Variable in Ihrem Debugger angezeigt (z. B. wenn Sie C-Code + Eclipse + gdb verwenden), und zwar als "optimierter Wert", da der Wert der Variablen jetzt ist irgendwo in einem Register. Wenn Sie nicht wissen, wie Assembler-Debugging-Tools / -Funktionen verwendet werden, deklarieren Sie Ihre Variable einfach mit dem flüchtigen Modifikator.

Antworten:

11

volatile bedeutet, dass ein anderer Prozessor oder ein anderes E / A-Gerät oder etwas anderes die Variable unter Ihnen ändern kann.

Bei einer normalen Variablen sind die Schritte Ihres Programms das einzige, was dies ändert. Wenn Sie beispielsweise 5aus einer Variablen lesen und diese nicht ändern, enthält sie immer noch 5. Da Sie sich darauf verlassen können, muss sich Ihr Programm nicht die Zeit nehmen, die Variable das nächste Mal erneut zu lesen, wenn Sie sie verwenden möchten. Der C ++ - Compiler ist intelligent, um Code zu generieren, der sich nur an das erinnert 5.

Aber Sie könnten es lesen als 5, dann lädt das System möglicherweise Daten von der Festplatte in diesen Speicher und ändert sie in 500. Wenn Sie möchten, dass Ihr Programm den neuen Wert liest 500, muss der Compiler nicht zu schlau sein, wenn er den zuvor gelesenen Wert verwendet 5. Sie müssen es anweisen, den Wert jedes Mal neu zu laden. Das volatilemacht es.

Eine Analogie für 5-Jährige
Nehmen wir an, Sie legen ein großes Blatt Papier auf einen Tisch. In einer Ecke des Papiers schreiben Sie die aktuelle Punktzahl eines laufenden Spiels auf 3 to 4. Dann gehen Sie zur gegenüberliegenden Seite des Tisches und schreiben eine Geschichte über das Spiel. Ihr Freund, der das Spiel beobachtet, aktualisiert die Punktzahl in dieser Ecke im Verlauf des Spiels. Sie löscht 3 to 4und schreibt 3 to 5.

Wenn Sie die Spielpunktzahl in Ihre Geschichte einfügen, können Sie entweder:

  1. Schreiben Sie die letzte Partitur auf, die Sie gelesen haben, 3 to 4und nehmen Sie fröhlich an, dass sie sich nicht geändert hat (oder dass es Ihnen nichts ausmacht, wenn dies der Fall ist), oder
  2. Gehen Sie zur gegenüberliegenden Seite des Tisches, um die aktuelle Punktzahl (die gerade 3 to 5jetzt ist) zu lesen , und gehen Sie zurück. So verhält sich eine volatileVariable.
Jerry101
quelle
11

volatile bedeutet zwei Dinge:

  1. Der Wert der Variablen kann sich ändern, ohne dass Ihr Code ihn ändert. Wenn der Compiler den Wert der Variablen liest, kann er daher möglicherweise nicht davon ausgehen, dass er mit dem zuletzt gelesenen Wert identisch ist oder mit dem zuletzt gespeicherten Wert identisch ist, er muss jedoch erneut gelesen werden.

  2. Das Speichern eines Werts in einer flüchtigen Variablen ist ein "Nebeneffekt", der von außen beobachtet werden kann, sodass der Compiler das Speichern eines Werts nicht entfernen darf. Wenn beispielsweise zwei Werte in einer Zeile gespeichert sind, muss der Compiler den Wert tatsächlich zweimal speichern.

Als Beispiel:

i = 2; 
i = i; 

Der Compiler muss die Nummer zwei speichern, die Variable i lesen und die Variable speichern, die er in i eingelesen hat.

Es gibt eine andere Situation: Wenn eine Funktion verwendet setjmpund dann longjmpaufgerufen wird, wird garantiert, dass bei allen flüchtigen lokalen Variablen der Funktion der letzte Wert gespeichert wird - dies ist bei nichtflüchtigen lokalen Variablen nicht der Fall.

gnasher729
quelle
Hier gibt es einige subtile Probleme. Ein subtiles Problem besteht darin, dass Sie flüchtige Lesevorgänge als Merkmal der Variablen charakterisiert haben, obwohl sie tatsächlich ein Merkmal dafür sind, wie auf die Variable zugegriffen wird . Wenn wir die Variable iund den Wert haben pi = &i, x = *piwird gelesen i, aber es wird nicht garantiert, dass dieser Lesevorgang eine flüchtige Semantik aufweist.
Eric Lippert
1
@EricLippert: Wenn ierklärt wird , wie volatile int idann pideklariert werden muß volatile int *pi, wobei in diesem Fall *piist ein flüchtiger Zugang, nicht wahr?
Jon Purdy
2

Abstrakte Erklärung
Sowohl C als auch C ++ haben das Konzept einer abstrakten Maschine . Wenn der Code den Wert einer Variablen verwendet, sagt die abstrakte Maschine, dass die Implementierung auf den Wert dieser Variablen zugreifen muss. Der Code des Formulars statement_A; statement_B; statement_C;muss genau in der angegebenen Reihenfolge ausgeführt werden. Ausdrücke, die diesen drei Anweisungen gemeinsam sind, müssen bei jedem Auftreten neu berechnet werden.

Gemäß den abstrakten Maschinen statement_A; statement_B; statement_C;muss die Implementierung angesichts der Reihenfolge der Anweisungen zuerst statement_Ain ihrer Gesamtheit, dann statement_Bund schließlich ausgeführt werden statement_C. Die Implementierung kann sich nicht erinnern, dass Sie ageden Wert 5 zugewiesen haben. Jede Anweisung, auf die verwiesen wird, agemuss stattdessen auf den Wert dieser Variablen zugreifen.

Das volatileSchlüsselwort wäre nicht erforderlich, wenn Implementierungen C- oder C ++ - Code gemäß den abstrakten Maschinenspezifikationen streng ausführen würden. Die abstrakten C- und C ++ - Maschinen haben kein Konzept von Registern, kein Konzept von allgemeinen Unterausdrücken, und die Ausführungsreihenfolge ist streng.

Beide Sprachen haben auch Als-ob- Regeln. Eine Implementierung entspricht dem Standard, solange sich diese Implementierung so verhält, als hätte sie Dinge gemäß der abstrakten Maschinenspezifikation ausgeführt. Der Compiler kann davon ausgehen, dass nichtflüchtige Variablen keine Werte zwischen Zuweisungen ändern. Solange die as-ifRegel nicht verletzt wird , kann die Sequenz statement_A; statement_B; statement_C;implementiert werden, indem ein Teil von statement_C, dann ein Teil von statement_A, dann alles von statement_B, dann der Rest von statement_Aund schließlich der Rest von ausgeführt wird statement_C.

Diese Als-ob- Regeln gelten nicht für volatileVariablen. In Bezug auf volatileVariablen und Funktionen muss eine Implementierung genau das tun, was Sie ihr gesagt haben, und genau in der Reihenfolge, in der Sie ihr gesagt haben, dass sie Dinge tun soll.

Die abstrakte Maschinenspezifikation hat einen Nachteil: Sie ist langsam. Ein positiver Aspekt von C und C ++ im Vergleich zu anderen Sprachen ist, dass sie ziemlich schnell sind. Dies wäre nicht der Fall, wenn Code für diese abstrakten Maschinen ausgeführt würde. Die Als-ob- Regeln ermöglichen es C und C ++, so schnell zu sein.

ELI5 Antwort

Was bedeutet es, wenn der Compiler die Speicheradresse nicht "wegoptimiert"?

Das "Optimieren" einer Speicheradresse ist ein fortschrittliches Konzept, das nicht im Bereich der Fähigkeiten eines Fünfjährigen liegt. Konforme Fünfjährige tun genau das, was Sie ihnen sagen, nicht mehr und nicht weniger. Mit volatilesagen Sie der Implementierung, dass sie sich wie fünf verhalten soll: Kein Denken, keine ausgefallenen Optimierungen. Stattdessen muss die Implementierung genau das tun, was der Code vorschreibt.

David Hammen
quelle
1

(nicht) flüchtig ist ein Hinweis für den Compiler, wie Code optimiert werden kann (aus Sicht des generierten Assembly-Codes):

  • Nichtflüchtig bedeutet, dass Ihr aktueller Compiler entscheidet, wo sich die Variable befindet oder wie der Wert der Variablen auf eine Unterroutine übertragen wird
    • in einer festen Speicheradresse,
    • auf dem Stapel [relativ zum aktuellen Stapelzeiger des Prozessors],
    • auf dem Heap [relativ zum aktuellen Basiszeiger des Prozessors],
    • in einem Prozessorregister,
    • ...
  • flüchtig bedeutet, dass der Compiler die Variable nicht optimieren kann, da etwas anderes außerhalb des Controllers der Haupt-CPU (dh ein separater Io-Prozessor) diesen Wert ändern kann.
k3b
quelle
0

Die Antworten scheinen ziemlich konsistent zu sein, aber es fehlt ein wichtiger Punkt. Sie teilen dem Compiler mit, dass Sie Speicherplatz zuweisen möchten, und für jeden Zugriff, lesen oder schreiben, möchten Sie, dass er diesen Zugriff ausführt. Wir möchten nicht, dass diese Zugriffe oder diese Variable aus irgendeinem Grund optimiert werden.

Ja, ein Grund ist, dass jemand anderes diesen Wert für uns ändern könnte. Ein weiterer Grund ist, dass wir diesen Wert möglicherweise für eine andere Person ändern. Dass jemand anderes es ist, der es für uns ändert oder für den wir es ändern, könnte Hardware / Logik oder Software sein. Es wird häufig verwendet, um Zugriffe auf Steuerungs- und Statusregister in eingebetteten Bare-Metal-Programmen zu definieren, die auf Hardware schreiben oder von dieser lesen. Sowie Software, die mit Software spricht, die in anderen Antworten erklärt wird.

Sie werden auch sehen, dass flüchtig verwendet wird, um zu steuern, wann und in welcher Reihenfolge die Zugriffe erfolgen, wenn Sie versuchen, einen Codeabschnitt zeitlich zu steuern, und Sie nicht flüchtig verwenden, müssen die fraglichen Variablen (Anfangszeit, Endzeit und Differenz) nur sein Gegen Ende berechnet, kann der Compiler eine der Zeitmessungen frei verschieben (nicht dort, wo wir sie platziert haben), nicht dass er nicht flüchtig ist, aber die Erfahrung zeigt, dass dies weniger wahrscheinlich ist.

Gelegentlich werden Sie sehen, dass es verwendet wurde, um einfach Zeit zu verbrennen. Ein elementarer LED-Blinker, die Hallo-Welt des Bare-Metal, verwendet möglicherweise einen flüchtigen Wert für eine Variable, die bis zu einer großen Anzahl zählt, nur um Zeit zu verbrennen, damit das menschliche Auge die LED sehen kann Zustand ändern. Fortgeschrittenere Beispiele verwenden dann Timer oder andere Ereignisse, um die Zeit zu verbrennen.

Oldtimer
quelle