it-swarm-eu.dev

Warum existiert volatile?

Was bewirkt das Schlüsselwort volatile? Welches Problem löst es in C++?

In meinem Fall habe ich es nie wissentlich gebraucht.

201
theschmitzer

volatile wird benötigt, wenn Sie von einer Stelle im Speicher lesen, auf die beispielsweise ein vollständig separater Prozess/Gerät/was auch immer geschrieben werden kann.

Ich habe mit Dual-Port-RAM in einem Multiprozessorsystem in Straight C gearbeitet. Wir verwendeten einen hardwaregesteuerten 16-Bit-Wert als Semaphor, um zu wissen, wann der andere Typ fertig war. Im Wesentlichen haben wir das gemacht:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Ohne volatile sieht der Optimierer die Schleife als nutzlos an (Der Typ setzt nie den Wert! Er ist verrückt, beseitigt den Code!), Und mein Code würde fortfahren, ohne das Semaphor erworben zu haben, was später Probleme verursachen würde.

247
Doug T.

volatile wird benötigt, wenn Sie eingebettete Systeme oder Gerätetreiber entwickeln, bei denen Sie ein speicherzugeordnetes Hardwaregerät lesen oder beschreiben müssen. Der Inhalt eines bestimmten Geräteregisters kann sich jederzeit ändern. Daher benötigen Sie das Schlüsselwort volatile, um sicherzustellen, dass solche Zugriffe nicht vom Compiler entfernt werden.

78
ChrisN

Einige Prozessoren haben Gleitkommaregister mit einer Genauigkeit von mehr als 64 Bit (z. B. 32-Bit x86 ohne SSE, siehe Peters Kommentar). Auf diese Weise erhalten Sie eine Antwort mit höherer Genauigkeit, als wenn Sie jedes Zwischenergebnis auf 64 Bit kürzen würden, wenn Sie mehrere Operationen mit doppeltgenauen Zahlen ausführen.

Dies ist normalerweise großartig, aber es bedeutet, dass Sie je nachdem, wie der Compiler Register zugewiesen und Optimierungen durchgeführt hat, unterschiedliche Ergebnisse für exakt dieselben Operationen mit exakt denselben Eingaben erhalten. Wenn Sie Konsistenz benötigen, können Sie jeden Vorgang mit dem Schlüsselwort volatile zwingen, in den Speicher zurückzukehren.

Es ist auch nützlich für einige Algorithmen, die keinen algebraischen Sinn ergeben, aber den Gleitkommafehler reduzieren, wie z. B. die Kahan-Summierung. Algebraisch ist es ein NOP, daher wird es oft falsch optimiert, es sei denn, einige Zwischenvariablen sind flüchtig.

68
tfinniga

Aus einem "Volatile as a promise" Artikel von Dan Saks:

(...) Ein flüchtiges Objekt ist eines, dessen Wert sich spontan ändern kann. Das heißt, wenn Sie ein Objekt als flüchtig deklarieren, teilen Sie dem Compiler mit, dass das Objekt möglicherweise den Status ändert, obwohl keine Anweisungen im Programm dies zu ändern scheinen. "

Hier sind Links zu drei seiner Artikel zum Schlüsselwort volatile:

45
MikeZ

Sie MÜSSEN volatile verwenden, wenn Sie sperrenfreie Datenstrukturen implementieren. Andernfalls kann der Compiler den Zugriff auf die Variable optimieren, wodurch sich die Semantik ändert.

Anders ausgedrückt, Volatile teilt dem Compiler mit, dass der Zugriff auf diese Variable einer Lese-/Schreiboperation im physischen Speicher entsprechen muss.

So wird beispielsweise InterlockedIncrement in der Win32-API deklariert:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);
23

Eine große Anwendung, an der ich Anfang der neunziger Jahre gearbeitet habe, enthielt die C-basierte Ausnahmebehandlung mit setjmp und longjmp. Das Schlüsselwort volatile war für Variablen erforderlich, deren Werte in dem als "catch" -Klausel dienenden Codeblock beibehalten werden mussten, damit diese Variablen in Registern gespeichert und vom longjmp gelöscht werden konnten.

10
Jeff Doar

In Standard C ist eine der Stellen, an denen volatile verwendet werden soll, ein Signalhandler. Tatsächlich können Sie in Standard C in einem Signalhandler nur eine volatile sig_atomic_t - Variable sicher ändern oder schnell beenden. Tatsächlich ist AFAIK die einzige Stelle in Standard C, an der die Verwendung von volatile erforderlich ist, um undefiniertes Verhalten zu vermeiden.

ISO/IEC 9899: 2011 §7.14.1.1 Die Funktion signal

¶5 Wenn das Signal nicht als Ergebnis des Aufrufs der Funktion abort oder raise auftritt, ist das Verhalten undefiniert, wenn der Signalhandler auf ein Objekt mit statischer oder Thread-Speicherdauer verweist, die nicht gültig ist Ein sperrfreies atomares Objekt, das sich von der Zuweisung eines Werts zu einem als volatile sig_atomic_t deklarierten Objekt unterscheidet, oder der Signalhandler ruft eine beliebige Funktion in der Standardbibliothek auf, die nicht die Funktion abort ist, die Funktion _Exit - Funktion, die Funktion quick_exit Oder die Funktion signal, wobei das erste Argument der Signalnummer entspricht, die dem Signal entspricht, das den Aufruf des Handlers verursacht hat. Wenn ein solcher Aufruf der Funktion signal zu einer SIG_ERR-Rückgabe führt, ist der Wert von errno unbestimmt.252)

252) Wenn ein Signal von einem asynchronen Signalhandler generiert wird, ist das Verhalten undefiniert.

Das bedeutet, dass Sie in Standard C schreiben können:

static volatile sig_atomic_t sig_num = 0;

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

und sonst nicht viel.

POSIX ist in Bezug auf die in einem Signalhandler möglichen Aktionen wesentlich nachgiebiger, es bestehen jedoch weiterhin Einschränkungen (und eine der Einschränkungen besteht darin, dass die Standard-E/A-Bibliothek - printf() et al - nicht sicher verwendet werden kann ).

10

Neben der bestimmungsgemäßen Verwendung wird Volatile bei der (Template-) Metaprogrammierung verwendet. Es kann verwendet werden, um versehentliches Überladen zu verhindern, da das flüchtige Attribut (wie const) an der Überladungsauflösung beteiligt ist.

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); }
};

Das ist legal; Beide Überladungen sind potenziell aufrufbar und verhalten sich fast gleich. Die Besetzung in der volatile -Überladung ist zulässig, da bekannt ist, dass der Balken ohnehin kein nichtflüchtiges T übergibt. Die volatile -Version ist jedoch strikt schlechter. Wählen Sie sie daher in der Überladungsauflösung nie aus, wenn die nichtflüchtige f verfügbar ist.

Beachten Sie, dass der Code niemals vom volatile Speicherzugriff abhängt.

7
MSalters

Ich habe es in Debugbuilds verwendet, wenn der Compiler darauf besteht, eine Variable zu optimieren, die ich sehen möchte, wenn ich durch den Code gehe.

7
indentation

Entwickelt für ein Embedded, habe ich eine Schleife, die eine Variable überprüft, die in einem Interrupt-Handler geändert werden kann. Ohne "flüchtig" wird die Schleife zu einem Noop - soweit der Compiler weiß, ändert sich die Variable nie und optimiert so das Abhaken.

Dasselbe gilt für eine Variable, die in einem anderen Thread in einer traditionelleren Umgebung geändert werden kann. Dort führen wir jedoch häufig Synchronisationsaufrufe durch, sodass der Compiler bei der Optimierung nicht so frei ist.

7
Arkadiy
  1. sie müssen es verwenden, um Spinlocks sowie einige (alle?) sperrenfreie Datenstrukturen zu implementieren
  2. verwenden Sie es mit atomaren Operationen/Anweisungen
  3. hat mir einmal geholfen, den Fehler des Compilers zu überwinden (falsch generierter Code während der Optimierung)
6

Das Schlüsselwort volatile soll den Compiler daran hindern, Optimierungen auf Objekte anzuwenden, die sich auf eine Weise ändern können, die vom Compiler nicht bestimmt werden kann.

Objekte, die als volatile deklariert wurden, werden bei der Optimierung nicht berücksichtigt, da ihre Werte jederzeit durch Code geändert werden können, der nicht zum aktuellen Code gehört. Das System liest immer den aktuellen Wert eines volatile -Objekts aus dem Speicherort, anstatt seinen Wert an dem Punkt, an dem es angefordert wird, im temporären Register zu belassen, selbst wenn ein vorheriger Befehl nach einem Wert von demselben Objekt gefragt hat.

Betrachten Sie die folgenden Fälle

1) Globale Variablen, die von einer Interrupt-Serviceroutine außerhalb des Gültigkeitsbereichs geändert wurden.

2) Globale Variablen innerhalb einer Multithread-Anwendung.

Wenn wir kein flüchtiges Qualifikationsmerkmal verwenden, können die folgenden Probleme auftreten

1) Code funktioniert möglicherweise nicht wie erwartet, wenn die Optimierung aktiviert ist.

2) Code funktioniert möglicherweise nicht wie erwartet, wenn Interrupts aktiviert und verwendet werden.

Flüchtig: der beste Freund eines Programmierers

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

4
roottraveller

Ihr Programm scheint auch ohne das Schlüsselwort volatile zu funktionieren? Vielleicht ist das der Grund:

Wie bereits erwähnt, hilft das Schlüsselwort volatile in Fällen wie

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

Sobald eine externe oder Nicht-Inline-Funktion aufgerufen wird, scheint dies jedoch so gut wie keine Auswirkungen zu haben. Z.B.:

while( *p!=0 ) { g(); }

Dann wird mit oder ohne volatile fast das gleiche Ergebnis erzeugt.

Solange g() vollständig eingebettet werden kann, kann der Compiler alles sehen, was gerade läuft, und kann daher optimieren. Wenn das Programm einen Aufruf an eine Stelle ausführt, die der Compiler nicht sehen kann In diesem Fall ist es für den Compiler nicht mehr sicher, irgendwelche Annahmen zu treffen, sodass der Compiler Code generiert, der immer direkt aus dem Speicher liest.

Aber hüten Sie sich vor dem Tag, an dem Ihre Funktion g() inline wird (entweder aufgrund expliziter Änderungen oder aufgrund der Klugheit von Compilern/Linkern), wenn Sie das volatile Stichwort!

Daher empfehle ich, das Schlüsselwort volatile hinzuzufügen, auch wenn Ihr Programm ohne zu funktionieren scheint. Es macht die Absicht klarer und robuster in Bezug auf zukünftige Änderungen.

2
Joachim

In den frühen Tagen von C interpretierten Compiler alle Aktionen, die Werte lesen und schreiben, als Speicheroperationen, die in derselben Reihenfolge wie die im Code angezeigten Lese- und Schreibvorgänge ausgeführt wurden. In vielen Fällen könnte die Effizienz erheblich verbessert werden, wenn den Compilern ein gewisses Maß an Freiheit eingeräumt würde, die Operationen neu zu ordnen und zu konsolidieren, aber dabei gab es ein Problem. Sogar Operationen wurden oft in einer bestimmten Reihenfolge angegeben, nur weil es notwendig war, sie in einiger Reihenfolge anzugeben, und daher wählte der Programmierer eine von vielen gleich guten Alternativen aus, was nicht immer der Fall war. Manchmal ist es wichtig, dass bestimmte Vorgänge in einer bestimmten Reihenfolge ausgeführt werden.

Welche Details der Sequenzierung wichtig sind, hängt von der Zielplattform und dem Anwendungsbereich ab. Anstatt eine besonders detaillierte Kontrolle zu bieten, entschied sich der Standard für ein einfaches Modell: Wenn eine Folge von Zugriffen mit nicht qualifizierten Werten durchgeführt wird volatile, kann ein Compiler diese nach Belieben neu anordnen und konsolidieren. Wenn eine Aktion mit einem volatile - qualifizierten Wert ausgeführt wird, sollte eine Qualitätsimplementierung alle zusätzlichen Bestellgarantien bieten, die für Code erforderlich sind, der auf die beabsichtigte Plattform und das Anwendungsfeld abzielt, ohne dass die Verwendung einer nicht standardmäßigen Syntax erforderlich ist .

Leider haben sich viele Compiler dafür entschieden, die vom Standard vorgeschriebenen Mindestgarantien anzubieten, anstatt festzustellen, welche Garantien Programmierer benötigen würden. Dies macht volatile weniger nützlich als es sein sollte. Auf gcc oder clang beispielsweise muss ein Programmierer, der ein grundlegendes "Hand-off-Mutex" implementieren muss (eines, bei dem eine Aufgabe, die ein Mutex erworben und freigegeben hat, dies erst dann erneut tut, wenn die andere Aufgabe dies getan hat), eines tun von vier Dingen:

  1. Setzen Sie die Erfassung und Freigabe des Mutex in eine Funktion, die der Compiler nicht einbinden kann und auf die er keine vollständige Programmoptimierung anwenden kann.

  2. Qualifizieren Sie alle durch den Mutex geschützten Objekte als volatile. Dies sollte nicht erforderlich sein, wenn alle Zugriffe nach dem Erfassen des Mutex und vor dem Freigeben erfolgen.

  3. Verwenden Sie die Optimierungsstufe 0, um den Compiler zum Generieren von Code zu zwingen, als wären alle nicht qualifizierten Objekte registervolatile.

  4. Verwenden Sie gcc-spezifische Anweisungen.

Im Gegensatz dazu hätte man bei Verwendung eines Compilers höherer Qualität, der besser für die Systemprogrammierung geeignet ist, wie z. B. icc, eine andere Option:

  1. Stellen Sie sicher, dass an jeder Stelle, an der ein Erwerb oder eine Freigabe erforderlich ist, ein volatile - qualifizierter Schreibvorgang ausgeführt wird.

Das Erwerb eines einfachen "Hand-off-Mutex" erfordert ein Lesen von volatile (um zu sehen, ob es fertig ist) und sollte auch kein Schreiben von volatile erfordern (die andere Seite wird es nicht versuchen) es neu erwerben, bis es zurückgegeben wird), aber einen bedeutungslosen volatile Schreibvorgang ausführen zu müssen, ist immer noch besser als jede der Optionen, die unter gcc oder clang verfügbar sind.

2
supercat

Neben der Tatsache, dass das Schlüsselwort volatile verwendet wird, um dem Compiler mitzuteilen, den Zugriff auf eine Variable nicht zu optimieren (die durch einen Thread oder eine Interruptroutine geändert werden kann), kann es auch verwendet werden, um einige Compiler-Fehler zu entfernen - JA es kann --- sein.

Ich habe zum Beispiel auf einer Embedded-Plattform gearbeitet, auf der der Compiler einige falsche Annahmen bezüglich des Werts einer Variablen gemacht hat. Wenn der Code nicht optimiert wäre, würde das Programm in Ordnung laufen. Mit Optimierungen (die wirklich notwendig waren, weil es eine kritische Routine war) würde der Code nicht richtig funktionieren. Die einzige (wenn auch nicht sehr korrekte) Lösung bestand darin, die 'fehlerhafte' Variable als flüchtig zu deklarieren.

2
INS

Eine Verwendung, die ich daran erinnern sollte, ist, dass Sie in der Signal-Handler-Funktion, wenn Sie auf eine globale Variable zugreifen/diese ändern möchten (zum Beispiel als exit = true markieren), diese Variable als 'flüchtig' deklarieren müssen.

1
bugs king