Was ist volatile (Typqualifizierer)?

Typqualifizierer in C, der dem Compiler mitteilt, dass sich der Wert einer Variablen außerhalb des sichtbaren Programms ändern kann (durch Peripherie, Interrupt-Handler usw.) oder dass schon das Lesen und Schreiben selbst eine Bedeutung hat. Zugriffe auf Objekte mit volatile werden weder weggelassen noch zusammengefasst, sondern in der geschriebenen Anzahl und Reihenfolge ausgeführt. Verwendet wird es für Register bei Memory-mapped I/O und für Flags, die mit einem Interrupt-Handler geteilt werden. Operationen atomar zu machen oder die Reihenfolge gegenüber anderen Kernen zu garantieren, leistet es nicht.

Japanische Bezeichnung: volatile(型修飾子)

„volatile (Typqualifizierer)“ kommt in 7 Übungsfragen aus 6 Themen vor, z. B. Grundlagen der Programmiersprache C, Betriebssystem und Hardware, Multicore und Synchronisation.

Fachgebiet: Grundlagen der Programmiersprache C

Was ohne volatile passiert

Ändert in einer Schleife wie while (flag == 0) { } nur ein Interrupt-Handler den Wert von flag, schließt der Compiler mitunter, dass sich flag in der Schleife nicht ändert, liest es nur einmal und macht daraus eine Endlosschleife. Dasselbe kann in einer Schleife passieren, die auf ein Statusregister der Peripherie wartet, und aufeinanderfolgende Schreibzugriffe auf dasselbe Register können zu einem zusammengefasst werden. Mit volatile wird so oft tatsächlich gelesen und geschrieben, wie es im Code steht.

Deklarationen lesen

volatile uint32_t *p ist ein „Zeiger auf volatile uint32_t“; Zugriffe auf *p werden nicht mehr weggelassen. Mit volatile uint32_t *const p kommt hinzu, dass der Zeiger selbst nicht geändert werden kann (er zeigt immer auf dasselbe Register). Häufig ist auch ein Makro, das eine Adresse in einen Zeiger umwandelt und dereferenziert, etwa #define REG (*(volatile uint32_t *)0x40020014).

Was volatile nicht leistet

volatile macht ein Read-Modify-Write nicht atomar; tritt mitten in *reg |= mask; ein Interrupt auf, kann die Änderung eines anderen Bits verloren gehen. Werte, die sich nicht in einem Zugriff lesen oder schreiben lassen, etwa eine 32-Bit-Variable auf einem 8-Bit-Mikrocontroller, können bei einer Unterbrechung teilweise schon den neuen Wert zeigen. Für die Synchronisierung zwischen Threads verwendet man Mechanismen, die Atomarität und Reihenfolge garantieren, etwa _Atomic aus C11 oder Sperren.

Jetzt üben: Fragen zu volatile (Typqualifizierer)

Warum genügt es in C nicht als Synchronisation, eine für den Austausch zwischen Threads verwendete Flag-Variable nur mit volatile zu versehen?

  1. AWeil mit volatile versehene Variablen von anderen Threads nicht mehr gelesen werden können
  2. BWeil volatile nur Compiler-Optimierungen unterdrückt und weder die Unteilbarkeit von Operationen noch die für andere Kerne sichtbare Reihenfolge garantiert
  3. CWeil eine mit volatile versehene Variable auf der Festplatte gespeichert wird und langsam ist
Antwort und Erläuterung anzeigen

Antwort: B. Weil volatile nur Compiler-Optimierungen unterdrückt und weder die Unteilbarkeit von Operationen noch die für andere Kerne sichtbare Reihenfolge garantiert

volatile weist den Compiler an, Lese- und Schreibzugriffe auf eine Variable weder wegzulassen noch zusammenzufassen, und war ursprünglich für den Zugriff auf Hardwareregister gedacht. Es verhindert nicht, dass die CPU die Reihenfolge von Speicheroperationen ändert, und macht auch Operationen wie counter++ nicht unteilbar. Ab C11 und C++11 garantieren _Atomic und std::atomic sowohl Unteilbarkeit als auch Speicherreihenfolge.

Häufige Fragen

Kann man const und volatile gleichzeitig angeben?

Ja. const volatile bedeutet „das Programm schreibt nicht hinein, aber der Wert ändert sich durch äußere Einflüsse und wird daher jedes Mal neu gelesen“ und wird etwa für schreibgeschützte Statusregister verwendet.

Wird ein Programm durch volatile langsamer?

Für die betreffende Variable ist die Optimierung, ihren Wert in einem Register zu halten, nicht mehr möglich; die Zahl der Zugriffe steigt, und es kann langsamer werden. Deshalb versieht man nicht alle Variablen damit, sondern nur die nötigen, etwa Peripherieregister und Variablen, die mit einem Interrupt-Handler geteilt werden.

„volatile (Typqualifizierer)“ in den Erläuterungen

Fragen mit „volatile (Typqualifizierer)“ (7)

  1. Warum wird beim Lesen eines Statusregisters eines Geräts per Memory-Mapped I/O wie oben volatile angegeben und der Bereich als nicht gecacht konfiguriert?
  2. Warum genügt es in C nicht als Synchronisation, eine für den Austausch zwischen Threads verwendete Flag-Variable nur mit volatile zu versehen?
  3. Welches Verfahren ordnet bei Mikrocontrollern usw. die Register von Peripheriegeräten demselben Adressraum wie den Speicher zu und bedient sie mit gewöhnlichen Speicherlese- und -schreibbefehlen?
  4. Beim Kompilieren des obigen Programms mit aktivierter Optimierung wird led_on() mitunter auch dann nicht aufgerufen, wenn ein Timer-Interrupt auftritt. Welche Kombination aus Ursache und Gegenmaßnahme ist korrekt?
  5. In Embedded-Programmen wird eine Variable für den Wert eines 32-Bit-Peripherieregisters oft nicht als unsigned int, sondern als uint32_t aus stdint.h deklariert. Was ist der Hauptgrund dafür?
  6. Bei einem Mikrocontroller wird das Ausgaberegister wie oben deklariert und verwendet. Welche Aussage über diese Deklaration ist richtig?
  7. Beim Messen der Rechengeschwindigkeit mit einer kleinen Schleife wie oben war die Zeit auch bei großem N nahezu 0. Was ist die wahrscheinlichste Ursache?

Verwandte Begriffe

Memory-Mapped I/O Variable Interrupt-Handler UART Interrupt (Unterbrechung) Mikrocontroller Zeiger (Pointer) Ausrichtung und Auffüllung (Alignment und Padding) Bitfeld Dangling Pointer (hängender Zeiger)

Zur Glossarübersicht