Warum wird volatile
in C benötigt? Was wird es verwendet? Was wird es tun?
c
declaration
volatile
Jonathan Leffler
quelle
quelle
Antworten:
Volatile weist den Compiler an, nichts zu optimieren, was mit der flüchtigen Variablen zu tun hat.
Es gibt mindestens drei häufige Gründe für die Verwendung: In allen Situationen kann sich der Wert der Variablen ändern, ohne dass der sichtbare Code eine Aktion ausführt: Wenn Sie eine Schnittstelle zu Hardware herstellen, die den Wert selbst ändert; wenn ein anderer Thread ausgeführt wird, der ebenfalls die Variable verwendet; oder wenn es einen Signalhandler gibt, der den Wert der Variablen ändern kann.
Angenommen, Sie haben ein kleines Stück Hardware, das irgendwo im RAM abgebildet ist und zwei Adressen hat: einen Befehlsport und einen Datenport:
Jetzt möchten Sie einen Befehl senden:
Sieht einfach aus, kann aber fehlschlagen, da der Compiler die Reihenfolge ändern kann, in der Daten und Befehle geschrieben werden. Dies würde dazu führen, dass unser kleines Gadget Befehle mit dem vorherigen Datenwert ausgibt. Schauen Sie sich auch die Wartezeit während der Besetztschleife an. Dieser wird optimiert. Der Compiler wird versuchen, klug zu sein, den Wert von isbusy nur einmal zu lesen und dann in eine Endlosschleife zu gehen. Das willst du nicht.
Um dies zu umgehen, müssen Sie das Zeiger-Gadget als flüchtig deklarieren. Auf diese Weise ist der Compiler gezwungen, das zu tun, was Sie geschrieben haben. Es kann die Speicherzuweisungen nicht entfernen, es kann keine Variablen in Registern zwischenspeichern und es kann auch die Reihenfolge der Zuweisungen nicht ändern:
Dies ist die richtige Version:
quelle
volatile
in C entstand tatsächlich, um die Werte der Variablen nicht automatisch zwischenzuspeichern. Der Compiler wird angewiesen, den Wert dieser Variablen nicht zwischenzuspeichern. Es wird also Code generiert, der beivolatile
jeder Begegnung den Wert der angegebenen Variablen aus dem Hauptspeicher entnimmt. Dieser Mechanismus wird verwendet, da der Wert jederzeit vom Betriebssystem oder einem Interrupt geändert werden kann. Die Verwendungvolatile
hilft uns also, jedes Mal neu auf den Wert zuzugreifen.quelle
volatile
war es, Compilern die Optimierung von Code zu ermöglichen und gleichzeitig Programmierern die Möglichkeit zu geben, die Semantik zu erreichen, die ohne solche Optimierungen erreicht werden würde. Die Autoren des Standards erwarteten, dass Qualitätsimplementierungen jede Semantik unterstützen würden, die angesichts ihrer Zielplattformen und Anwendungsfelder nützlich ist, und erwarteten nicht, dass Compiler-Autoren versuchen würden, die Semantik mit der niedrigsten Qualität anzubieten, die dem Standard entspricht und nicht 100% ist dumm (beachten Sie, dass die Autoren des Standards ausdrücklich in der Begründung anerkennen ...Eine andere Verwendung für
volatile
ist Signalhandler. Wenn Sie folgenden Code haben:Der Compiler darf feststellen, dass der Schleifenkörper die
quit
Variable nicht berührt , und die Schleife in einewhile (true)
Schleife konvertieren . Auch wenn diequit
Variable im Signalhandler fürSIGINT
und gesetzt istSIGTERM
; Der Compiler kann das nicht wissen.Wenn die
quit
Variable jedoch deklariert istvolatile
, muss der Compiler sie jedes Mal laden, da sie an anderer Stelle geändert werden kann. Dies ist genau das, was Sie in dieser Situation wollen.quelle
quit
, kann der Compiler sie unter der Annahme in eine konstante Schleife optimieren dass es keine Möglichkeit gibt,quit
zwischen Iterationen geändert zu werden. NB: Dies ist nicht unbedingt ein guter Ersatz für die eigentliche threadsichere Programmierung.volatile
wenn keine oder andere Markierungen vorhanden sind, wird davon ausgegangen, dass nichts außerhalb der Schleife diese Variable ändert, sobald sie in die Schleife eintritt, selbst wenn es sich um eine globale Variable handelt.extern int global; void fn(void) { while (global != 0) { } }
mitgcc -O3 -S
der resultierenden Assembly-Datei zu kompilieren und sie anzusehen. Auf meinem Computer ist dies der Fallmovl global(%rip), %eax
.testl %eax, %eax
;;je .L1
;;.L4: jmp .L4
Das heißt, eine Endlosschleife, wenn die globale nicht Null ist. Versuchen Sie dann hinzuzufügenvolatile
und sehen Sie den Unterschied.volatile
teilt dem Compiler mit, dass Ihre Variable auf andere Weise geändert werden kann als durch den Code, der darauf zugreift. Beispielsweise kann es sich um einen E / A-zugeordneten Speicherort handeln. Wenn dies in solchen Fällen nicht angegeben ist, können einige variable Zugriffe optimiert werden, z. B. kann der Inhalt in einem Register gespeichert werden und der Speicherort wird nicht erneut eingelesen.quelle
Siehe diesen Artikel von Andrei Alexandrescu, " volatile - Multithreaded Programmer's Best Friend "
Der Artikel gilt sowohl für
C
als auchC++
.Siehe auch den Artikel " C ++ und die Gefahren des Double-Checked Locking " von Scott Meyers und Andrei Alexandrescu:
quelle
volatile
garantiert keine Atomizität.Meine einfache Erklärung lautet:
In einigen Szenarien optimiert der Compiler basierend auf der Logik oder dem Code Variablen, von denen er glaubt, dass sie sich nicht ändern. Das
volatile
Schlüsselwort verhindert, dass eine Variable optimiert wird.Zum Beispiel:
Aus dem obigen Code geht der Compiler möglicherweise davon aus, dass er
usb_interface_flag
als 0 definiert ist und dass er in der while-Schleife für immer Null ist. Nach der Optimierung wird es vom Compiler wie immer behandeltwhile(true)
, was zu einer Endlosschleife führt.Um diese Art von Szenarien zu vermeiden, deklarieren wir das Flag als flüchtig. Wir teilen dem Compiler mit, dass dieser Wert durch eine externe Schnittstelle oder ein anderes Programmmodul geändert werden kann, dh bitte nicht optimieren. Das ist der Anwendungsfall für volatile.
quelle
Eine marginale Verwendung für flüchtige Stoffe ist die folgende. Angenommen, Sie möchten die numerische Ableitung einer Funktion berechnen
f
:Das Problem ist, dass aufgrund von Rundungsfehlern im
x+h-x
Allgemeinen nicht gleichh
ist. Denken Sie darüber nach: Wenn Sie sehr nahe Zahlen subtrahieren, verlieren Sie viele signifikante Ziffern, die die Berechnung der Ableitung ruinieren können (denken Sie an 1.00001 - 1). Eine mögliche Problemumgehung könnte seinAbhängig von Ihrer Plattform und den Compiler-Switches kann die zweite Zeile dieser Funktion von einem aggressiv optimierenden Compiler gelöscht werden. Also schreibst du stattdessen
um den Compiler zu zwingen, den Speicherort zu lesen, der hh enthält, verfällt eine mögliche Optimierungsmöglichkeit.
quelle
h
oder derhh
abgeleiteten Formel? Wennhh
berechnet wird, verwendet die letzte Formel sie wie die erste, ohne Unterschied. Vielleicht sollte es sein(f(x+h) - f(x))/hh
?h
undhh
besteht darin, dasshh
durch die Operation eine negative Zweierpotenz abgeschnitten wirdx + h - x
. In diesem Fallx + hh
undx
unterscheiden sich genau umhh
. Sie können auch Ihre Formel nehmen, sie ergibt das gleiche Ergebnis, dax + h
undx + hh
gleich sind (es ist der Nenner, der hier wichtig ist).x1=x+h; d = (f(x1)-f(x))/(x1-x)
? ohne das flüchtige zu verwenden.-ffast-math
oder gleichwertig angegeben wird.Es gibt zwei Verwendungszwecke. Diese werden besonders häufig in der Embedded-Entwicklung eingesetzt.
Der Compiler optimiert nicht die Funktionen, die Variablen verwenden, die mit dem Schlüsselwort volatile definiert sind
Volatile wird verwendet, um auf genaue Speicherorte im RAM, ROM usw. zuzugreifen. Dies wird häufiger verwendet, um speicherabgebildete Geräte zu steuern, auf CPU-Register zuzugreifen und bestimmte Speicherorte zu lokalisieren.
Siehe Beispiele mit Baugruppenliste. Betreff: Verwendung des Schlüsselworts "volatile" C in der eingebetteten Entwicklung
quelle
Volatile ist auch nützlich, wenn Sie den Compiler zwingen möchten, eine bestimmte Codesequenz nicht zu optimieren (z. B. zum Schreiben eines Mikro-Benchmarks).
quelle
Ich werde ein anderes Szenario erwähnen, in dem flüchtige Stoffe wichtig sind.
Angenommen, Sie ordnen eine Datei für eine schnellere E / A zu und diese Datei kann sich hinter den Kulissen ändern (z. B. befindet sich die Datei nicht auf Ihrer lokalen Festplatte, sondern wird stattdessen von einem anderen Computer über das Netzwerk bereitgestellt).
Wenn Sie über Zeiger auf nichtflüchtige Objekte (auf Quellcodeebene) auf die Daten der Speicherzuordnungsdatei zugreifen, kann der vom Compiler generierte Code dieselben Daten mehrmals abrufen, ohne dass Sie sich dessen bewusst sind.
Wenn sich diese Daten ändern, verwendet Ihr Programm möglicherweise zwei oder mehr verschiedene Versionen der Daten und gerät in einen inkonsistenten Zustand. Dies kann nicht nur zu einem logisch inkorrekten Verhalten des Programms führen, sondern auch zu ausnutzbaren Sicherheitslücken, wenn es nicht vertrauenswürdige Dateien oder Dateien von nicht vertrauenswürdigen Speicherorten verarbeitet.
Wenn Sie Wert auf Sicherheit legen und dies sollten, ist dies ein wichtiges Szenario.
quelle
flüchtig bedeutet, dass sich der Speicher wahrscheinlich jederzeit ändert und geändert wird, jedoch außerhalb der Kontrolle des Benutzerprogramms. Dies bedeutet, dass das Programm, wenn Sie auf die Variable verweisen, immer die physikalische Adresse (dh eine zugeordnete Eingabe-Fifo) überprüfen und nicht zwischengespeichert verwenden sollte.
quelle
Das Wiki sagt alles über
volatile
:Und das Dokument des Linux-Kernels bietet auch eine hervorragende Notation über
volatile
:quelle
Meiner Meinung nach sollte man nicht zu viel erwarten
volatile
. Schauen Sie sich zur Veranschaulichung das Beispiel in Nils Pipenbrincks hochgewählter Antwort an .Ich würde sagen, sein Beispiel ist nicht geeignet für
volatile
.volatile
wird nur verwendet, um: den Compiler daran zu hindern, nützliche und wünschenswerte Optimierungen vorzunehmen . Es geht nicht um den Thread-sicheren, atomaren Zugriff oder sogar die Speicherreihenfolge.In diesem Beispiel:
Das
gadget->data = data
Vorherigegadget->command = command
wird nur im kompilierten Code vom Compiler garantiert. Zur Laufzeit ordnet der Prozessor möglicherweise noch die Daten- und Befehlszuweisung in Bezug auf die Prozessorarchitektur neu an. Die Hardware könnte die falschen Daten erhalten (angenommen, das Gadget ist der Hardware-E / A zugeordnet). Die Speicherbarriere wird zwischen Daten- und Befehlszuweisung benötigt.quelle
volatile
als würde die Leistung ohne Grund beeinträchtigt. Ob dies ausreicht, hängt von anderen Aspekten des Systems ab, über die der Programmierer möglicherweise mehr weiß als der Compiler. Wenn andererseits ein Prozessor garantiert, dass eine Anweisung zum Schreiben an eine bestimmte Adresse den CPU-Cache leert, ein Compiler jedoch keine Möglichkeit bietet, registrierungsgespeicherte Variablen zu leeren, von denen die CPU nichts weiß, wäre das Leeren des Caches nutzlos.In der von Dennis Ritchie entworfenen Sprache verhält sich jeder Zugriff auf ein Objekt außer automatischen Objekten, deren Adresse nicht vergeben wurde, so, als würde er die Adresse des Objekts berechnen und dann den Speicher an dieser Adresse lesen oder schreiben. Dies machte die Sprache sehr leistungsfähig, aber die Optimierungsmöglichkeiten stark eingeschränkt.
Während es möglich gewesen wäre, ein Qualifikationsmerkmal hinzuzufügen, das einen Compiler auffordert, anzunehmen, dass ein bestimmtes Objekt nicht auf seltsame Weise geändert wird, wäre eine solche Annahme für die überwiegende Mehrheit der Objekte in C-Programmen angemessen, und dies wäre der Fall Es war unpraktisch, allen Objekten, für die eine solche Annahme angemessen wäre, ein Qualifikationsmerkmal hinzuzufügen. Andererseits müssen einige Programme einige Objekte verwenden, für die eine solche Annahme nicht gelten würde. Um dieses Problem zu beheben, geht der Standard davon aus, dass Compiler davon ausgehen können, dass bei Objekten, die nicht deklariert
volatile
sind, ihr Wert nicht auf eine Weise beobachtet oder geändert wird, die außerhalb der Kontrolle des Compilers liegt oder außerhalb des Verständnisses eines vernünftigen Compilers liegt.Da verschiedene Plattformen unterschiedliche Möglichkeiten haben, Objekte außerhalb der Kontrolle eines Compilers zu beobachten oder zu ändern, sollten sich Qualitätscompiler für diese Plattformen in ihrer genauen Handhabung der
volatile
Semantik unterscheiden. Da der Standard nicht vorschlug, dass Qualitätscompiler, die für die Programmierung auf niedriger Ebene auf einer Plattform vorgesehen sind, so behandelt werden solltenvolatile
, dass alle relevanten Auswirkungen eines bestimmten Lese- / Schreibvorgangs auf dieser Plattform erkannt werden, sind viele Compiler leider nicht in der Lage, dies zu tun Auf eine Weise, die es schwieriger macht, Dinge wie Hintergrund-E / A auf eine Weise zu verarbeiten, die effizient ist, aber nicht durch Compiler- "Optimierungen" unterbrochen werden kann.quelle
In einfachen Worten, es weist den Compiler an, keine Optimierung für eine bestimmte Variable vorzunehmen. Variablen, die dem Geräteregister zugeordnet sind, werden vom Gerät indirekt geändert. In diesem Fall muss flüchtig verwendet werden.
quelle
Ein flüchtiger Bestandteil kann von außerhalb des kompilierten Codes geändert werden (z. B. kann ein Programm eine flüchtige Variable einem Speicherabbildungsregister zuordnen.) Der Compiler wendet bestimmte Optimierungen nicht auf Code an, der eine flüchtige Variable verarbeitet - zum Beispiel hat er gewonnen. t Laden Sie es in ein Register, ohne es in den Speicher zu schreiben. Dies ist wichtig beim Umgang mit Hardwareregistern.
quelle
Wie von vielen hier zu Recht vorgeschlagen, besteht die beliebte Verwendung des flüchtigen Schlüsselworts darin, die Optimierung der flüchtigen Variablen zu überspringen.
Der beste Vorteil , dass in den Sinn kommt, und erwähnenswert , nach etwa flüchtigen Lesen ist - zu verhindern ein Zurückrollen der Variablen im Falle eines
longjmp
. Ein nicht lokaler Sprung.Was bedeutet das?
Dies bedeutet einfach, dass der letzte Wert nach dem Abwickeln des Stapels beibehalten wird, um zu einem vorherigen Stapelrahmen zurückzukehren. in der Regel im Falle eines fehlerhaften Szenarios.
Da dies nicht in den Rahmen dieser Frage fällt, gehe ich hier nicht auf Details
setjmp/longjmp
ein, aber es lohnt sich, darüber zu lesen. und wie die Volatilitätsfunktion verwendet werden kann, um den letzten Wert beizubehalten.quelle
Der Compiler kann die Werte von Variablen nicht automatisch ändern. Eine flüchtige Variable dient der dynamischen Verwendung.
quelle