it-swarm.com.de

Warum wird in C Flüchtigkeit benötigt?

Warum wird volatile in C benötigt? Was wird es verwendet? Was wird es tun?

382
thomas

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, die alle Situationen betreffen, in denen sich der Wert der Variablen ohne Aktion über den sichtbaren Code ändern kann: Wenn Sie eine Schnittstelle mit Hardware herstellen, die den Wert selbst ändert; wenn ein anderer Thread ausgeführt wird, der auch die Variable verwendet; oder wenn es einen Signalhandler gibt, der den Wert der Variablen ändern kann.

Angenommen, Sie haben eine kleine Hardware, die irgendwo in RAM) abgebildet ist und zwei Adressen hat: einen Befehlsanschluss und einen Datenanschluss:

typedef struct
{
  int command;
  int data;
  int isbusy;
} MyHardwareGadget;

Nun möchten Sie einen Befehl senden:

void SendCommand (MyHardwareGadget * gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isbusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

Sieht einfach aus, kann jedoch 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 Warteschleife an. Dieser wird optimiert. Der Compiler wird versuchen, clever zu sein, den Wert von isbusy nur einmal zu lesen und dann in eine Endlosschleife zu gelangen. Das ist nicht was du willst.

Um dies zu umgehen, müssen Sie das Zeiger-Gadget als flüchtig deklarieren. Auf diese Weise wird 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:

   void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }
376

volatile in C ist tatsächlich entstanden, um die Werte der Variablen nicht automatisch zwischenzuspeichern. Der Compiler wird angewiesen, den Wert dieser Variablen nicht zwischenzuspeichern. Daher wird Code generiert, der den Wert der angegebenen Variablen volatile jedes Mal aus dem Hauptspeicher entnimmt, wenn er darauf trifft. Dieser Mechanismus wird verwendet, da der Wert jederzeit vom Betriebssystem oder einem Interrupt geändert werden kann. Die Verwendung von volatile hilft uns also, jedes Mal aufs Neue auf den Wert zuzugreifen.

174
Manoj Doubts

Eine andere Verwendung für volatile sind Signalhandler. Wenn Sie Code wie diesen haben:

int quit = 0;
while (!quit)
{
    /* very small loop which is completely visible to the compiler */
}

Der Compiler darf feststellen, dass der Schleifenkörper die Variable quit nicht berührt und die Schleife in eine while (true) -Schleife konvertiert. Auch wenn die Variable quit im Signalhandler für SIGINT und SIGTERM gesetzt ist; Der Compiler kann das nicht wissen.

Wenn jedoch die Variable quit als volatile deklariert wird, muss sie der Compiler jedes Mal laden, da sie an anderer Stelle geändert werden kann. Genau das möchten Sie in dieser Situation.

166
CesarB

volatile teilt dem Compiler mit, dass Ihre Variable auf andere Weise geändert werden kann als durch den Code, der darauf zugreift. es kann sich beispielsweise um einen Speicherort mit E/A-Zuordnung handeln. Wenn dies in solchen Fällen nicht spezifiziert ist, können einige variable Zugriffe optimiert werden, z. B. kann sein Inhalt in einem Register gehalten werden, und der Speicherort kann nicht wieder eingelesen werden.

56

Siehe diesen Artikel von Andrei Alexandrescu, " volatile - Multithreaded Programmer's Best Friend "

Das flüchtig Das Schlüsselwort wurde entwickelt, um Compiler-Optimierungen zu verhindern, die bei bestimmten asynchronen Ereignissen zu einer fehlerhaften Darstellung des Codes führen können. Wenn Sie beispielsweise eine primitive Variable als deklarieren flüchtigIst es dem Compiler nicht gestattet, ihn in einem Register zwischenzuspeichern - eine übliche Optimierung, die katastrophal wäre, wenn diese Variable von mehreren Threads gemeinsam genutzt würde. Die allgemeine Regel lautet also: Wenn Sie Variablen des primitiven Typs haben, die von mehreren Threads gemeinsam genutzt werden müssen, deklarieren Sie diese Variablen flüchtig. Mit diesem Schlüsselwort können Sie jedoch noch viel mehr tun: Sie können damit Code abfangen, der nicht threadsicher ist, und dies können Sie beim Kompilieren tun. Dieser Artikel zeigt, wie es gemacht wird; Die Lösung besteht aus einem einfachen intelligenten Zeiger, mit dem sich auch wichtige Codeabschnitte problemlos serialisieren lassen.

Der Artikel gilt sowohl für C als auch für C++.

Siehe auch den Artikel " C++ und die Gefahren des Double-Checked Locking " von Scott Meyers und Andrei Alexandrescu:

Wenn also einige Speicherorte (z. B. Ports mit Speicherzuordnung oder Speicher, auf den von ISRs [Interrupt Service Routines] verwiesen wird) behandelt werden, müssen einige Optimierungen ausgesetzt werden. volatile existiert, um eine spezielle Behandlung für solche Speicherorte zu spezifizieren, und zwar: (1) der Inhalt einer flüchtigen Variablen ist "instabil" (kann sich durch dem Compiler unbekannte Mittel ändern), (2) alle Schreibvorgänge in flüchtige Daten sind "beobachtbar", so dass sie "beobachtbar" sind müssen religiös ausgeführt werden, und (3) alle Operationen an flüchtigen Daten werden in der Reihenfolge ausgeführt, in der sie im Quellcode erscheinen. Die ersten beiden Regeln sorgen für korrektes Lesen und Schreiben. Das letzte ermöglicht die Implementierung von E/A-Protokollen, die Eingabe und Ausgabe mischen. Dies ist informell, was die Flüchtigkeit von C und C++ garantiert.

30

Meine einfache Erklärung lautet:

In einigen Szenarien optimiert der Compiler basierend auf der Logik oder dem Code Variablen, die seiner Meinung nach nicht geändert werden. Das Schlüsselwort volatile verhindert, dass eine Variable optimiert wird.

Zum Beispiel:

bool usb_interface_flag = 0;
while(usb_interface_flag == 0)
{
    // execute logic for the scenario where the USB isn't connected 
}

Aus dem obigen Code kann der Compiler annehmen, dass usb_interface_flag Als 0 definiert ist und dass es in der while-Schleife für immer Null sein wird. Nach der Optimierung behandelt der Compiler es die ganze Zeit als while(true), was zu einer Endlosschleife führt.

Um diese Art von Szenarien zu vermeiden, deklarieren wir das Flag als flüchtig und teilen dem Compiler mit, dass dieser Wert von einer externen Schnittstelle oder einem anderen Programmmodul geändert werden kann, d. H., Optimieren Sie ihn bitte nicht. Das ist der Anwendungsfall für volatile.

Eine marginale Verwendung für flüchtige Stoffe ist die folgende. Angenommen, Sie möchten die numerische Ableitung einer Funktion f berechnen:

double der_f(double x)
{
    static const double h = 1e-3;
    return (f(x + h) - f(x)) / h;
}

Das Problem ist, dass x+h-x ist im Allgemeinen aufgrund von Rundungsfehlern nicht gleich h. Denken Sie darüber nach: Wenn Sie sehr enge Zahlen subtrahieren, verlieren Sie viele signifikante Ziffern, was die Berechnung der Ableitung ruinieren kann (denken Sie an 1.00001 - 1). Ein möglicher Workaround könnte sein

double der_f2(double x)
{
    static const double h = 1e-3;
    double hh = x + h - x;
    return (f(x + hh) - f(x)) / hh;
}

je nach Plattform und Compiler-Switches wird die zweite Zeile dieser Funktion möglicherweise von einem aggressiv optimierenden Compiler gelöscht. Also schreibst du stattdessen

    volatile double hh = x + h;
    hh -= x;

um den Compiler zu zwingen, den Speicherort zu lesen, der hh enthält, verfällt eine eventuelle Optimierungsmöglichkeit.

19
Alexandre C.

Es gibt zwei Verwendungszwecke. Diese werden speziell in der Embedded-Entwicklung häufiger eingesetzt.

  1. Der Compiler optimiert nicht die Funktionen, die Variablen verwenden, die mit dem Schlüsselwort volatile definiert wurden

  2. Volatile wird verwendet, um auf genaue Speicherorte im RAM, ROM usw. zuzugreifen. Dies wird häufiger verwendet, um Geräte mit Speicherzuordnung zu steuern, auf CPU-Register zuzugreifen und bestimmte Speicherorte zu lokalisieren.

Siehe Beispiele mit Baugruppenliste. Re: Verwendung von C "volatile" Keyword in Embedded Development

11
Neo Cambell

Flüchtig ist auch nützlich, wenn Sie den Compiler zwingen möchten, eine bestimmte Codesequenz nicht zu optimieren (z. B. zum Schreiben eines Micro-Benchmarks).

10

Ich werde ein anderes Szenario erwähnen, in dem flüchtige Stoffe wichtig sind.

Angenommen, Sie ordnen einer Datei eine Speicherzuordnung für schnellere E/A-Vorgänge zu und diese Datei kann sich im Hintergrund ändern (z. B. befindet sich die Datei nicht auf Ihrer lokalen Festplatte, sondern wird über das Netzwerk von einem anderen Computer 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 dies bemerken.

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 logisch inkorrektem 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 auch tun sollten, ist dies ein wichtiges Szenario.

10
Alexey Frunze

flüchtig bedeutet, dass sich der Speicher jederzeit ändern und geändert werden kann, jedoch außerhalb der Kontrolle des Anwenderprogramms. Das heißt, wenn Sie auf die Variable verweisen, sollte das Programm immer die physikalische Adresse (dh ein zugeordnetes Eingabe-Fifo) überprüfen und nicht im Cache verwenden.

7

Das Wiki sagt alles über volatile:

Und das Dokument des Linux-Kernels enthält auch eine hervorragende Anmerkung zu volatile:

5
coanor

In einfachen Worten, es weist den Compiler an, keine Optimierung für eine bestimmte Variable durchzuführen. Variablen, die dem Geräteregister zugeordnet sind, werden vom Gerät indirekt geändert. In diesem Fall muss flüchtig verwendet werden.

5
rajeshsam

In der von Dennis Ritchie entworfenen Sprache verhält sich jeder Zugriff auf ein Objekt, mit Ausnahme von automatischen Objekten, deren Adresse nicht vergeben wurde, so, als würde die Adresse des Objekts berechnet und dann der Speicher an dieser Adresse gelesen oder geschrieben. Dies machte die Sprache sehr leistungsfähig, aber stark eingeschränkte Optimierungsmöglichkeiten.

Während es möglich gewesen wäre, ein Qualifikationsmerkmal hinzuzufügen, das einen Compiler dazu 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 auch der Fall Es war unpraktisch, allen Objekten, für die eine solche Annahme angemessen wäre, einen Qualifikator 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 Objekte, die nicht als volatile deklariert sind, ihren Wert nicht beobachten oder auf eine Weise ändern, die außerhalb der Kontrolle des Compilers liegt oder außerhalb des Verständnisses eines vernünftigen Compilers liegt.

Da es auf verschiedenen Plattformen unterschiedliche Möglichkeiten gibt, Objekte außerhalb der Kontrolle eines Compilers zu beobachten oder zu ändern, sollten sich die Qualitätscompiler für diese Plattformen hinsichtlich der genauen Behandlung der Semantik von volatile unterscheiden. Leider, da der Standard nicht vorschlug, dass Qualitätscompiler, die für Low-Level-Programmierung auf einer Plattform vorgesehen sind, volatile so behandeln sollten, dass alle relevanten Auswirkungen eines bestimmten Lese-/Schreibvorgangs auf dieser Plattform erkannt werden. Viele Compiler tun dies nicht 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" zerstört werden kann.

5
supercat

Meiner Meinung nach sollten Sie von volatile nicht zu viel erwarten. Schauen Sie sich zur Veranschaulichung das Beispiel in Nils Pipenbrincks hochgelobte Antwort an.

Ich würde sagen, sein Beispiel ist nicht für volatile geeignet. volatile wird nur verwendet, um: zu verhindern, dass der Compiler nützliche und wünschenswerte Optimierungen vornimmt . Es geht nicht um den Thread-sicheren, atomaren Zugriff oder gar die Speicherreihenfolge.

In diesem Beispiel:

    void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
    {
      // wait while the gadget is busy:
      while (gadget->isbusy)
      {
        // do nothing here.
      }
      // set data first:
      gadget->data    = data;
      // writing the command starts the action:
      gadget->command = command;
    }

das gadget->data = data Vor gadget->command = command only wird nur im kompilierten Code vom Compiler garantiert. Zur Laufzeit ordnet der Prozessor möglicherweise noch die Daten- und Befehlszuordnung in Bezug auf die Prozessorarchitektur neu. Die Hardware könnte die falschen Daten erhalten (Angenommen, das Gadget ist der Hardware-E/A zugeordnet). Die Speichersperre wird zwischen Daten- und Befehlszuordnung benötigt.

5
Oliver

Ein flüchtiger Code kann von außerhalb des kompilierten Codes geändert werden (z. B. kann ein Programm eine flüchtige Variable einem Register mit Speicherzuordnung zuordnen.) Der Compiler wendet bestimmte Optimierungen nicht auf Code an, der eine flüchtige Variable verarbeitet. t Laden Sie es in ein Register, ohne es in den Speicher zu schreiben. Dies ist wichtig beim Umgang mit Hardwareregistern.

3
Ori Pessach