Der Volatile Qualifier in C
Das heutige Tutorial behandelt den volatile
Qualifier in C. Wir werden verstehen, wie und wo wir diesen Qualifier in der C-Programmierung verwenden können.
der volatile
Qualifier in C
Wir verwenden den flüchtigen Qualifizierer erst, wenn wir an der Low-Level-Programmierung arbeiten. Low-Level-Programmierung bedeutet hier den Code, der mit den IO-Ports und Interrupt Service Routines (ISRs) umgehen muss, die mit der Hardware interagieren sollen.
Wir alle wissen, dass der Compiler den C-Code in den Maschinencode umwandelt, sodass die ausführbare Datei ohne die Verfügbarkeit des Quellcodes ausgeführt werden kann.
Wie andere Technologien wandelt auch der Compiler für die C-Programmierung den Quellcode in Maschinencode um. Hier tut sich der Compiler typischerweise schwer, das Ergebnis (Ausgabe) so zu optimieren, dass am Ende nur noch minimaler Maschinencode ausgeführt werden muss.
Diese Art der Optimierung entfernt den unnötigen Maschinencode für den Zugriff auf die Variable, die aus Sicht des Compilers nicht aktualisiert wird.
Beispielcode:
int main() {
int status = 0;
while (status == 0) {
}
}
Der optimierende Compiler wird feststellen, dass die Variable mit dem Namen status
nicht in einer while
-Schleife im obigen Code aktualisiert wird. Es ist also nicht erforderlich, bei jeder Iteration auf diese Variable zuzugreifen.
Die Schleife würde vom Compiler in eine Endlosschleife (while(1)
) umgewandelt, so dass der Maschinencode zum Lesen der status
-Variablen nicht benötigt wird.
Der Compiler weiß nicht, dass die Variable status
auch im laufenden Programm an jeder Stelle ausserhalb der Schleife aktualisiert werden kann. Zum Beispiel, wenn eine IO-Operation auf dem Peripheriegerät auftritt.
Praktisch möchten wir, dass der Compiler bei jeder Iteration Zugriff auf eine Variable namens status
erhält, obwohl das Programm sie nicht ändert. Jetzt haben Sie vielleicht den Vorschlag, alle Kompilierungsoptimierungen für solche C-Programme auszuschalten, um diese Art von Situation zu vermeiden, aber das ist aus den folgenden Gründen nicht die Lösung.
- Die Implementierung der Compiler variiert von einem zum anderen.
- Das Deaktivieren aller Compiler-Optimierungen nur wegen einer Variablen kann zu Problemen führen, da einige dieser Optimierungen möglicherweise in einem Teil eines anderen Programms benötigt werden.
- Aufgrund der Deaktivierung der Compiler-Optimierungen kann die Low-Level-Anwendung nicht so funktionieren, wie sie sollte. Zum Beispiel verzögerte Ausführung.
Hier brauchen wir den Qualifier volatile
. Das Schlüsselwort volatile
ist nichts anderes als ein Qualifier, mit dem wir (als Programmierer) dem Compiler mitteilen, dass für den status
keine Optimierungen erlaubt sind und wird wie folgt verwendet.
volatile int status = 0;
Wer diesen Qualifier verwendet, muss die folgenden Eigenschaften von volatile
berücksichtigen.
- Es kann die Speicherbelegung nicht aufheben.
- Die Variablen können nicht im Register zwischengespeichert werden.
- Der Wert kann in der Reihenfolge der Zuweisung nicht geändert werden.
Verwendung des Qualifiers volatile
in der C-Programmierung
Im folgenden Code drucken wir eine Nachricht mit der Aufschrift Warten...
, bis der Thread die Variable mit dem Namen done
ändert, und geben dann eine weitere Nachricht mit der Aufschrift "Okay, let's move on"
aus.
Beispielcode (ohne volatile
):
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
bool done = false;
void *tfunc() {
sleep(1);
done = true;
return NULL;
}
int main() {
pthread_t t1;
pthread_create(&t1, NULL, tfunc, NULL);
printf("Waiting...\n");
while (!done) {
}
printf("Okay, Let's move on");
}
Kompilieren Sie dieses Programm und führen Sie es aus.
PS C:\Users\DelftStack\Desktop\C> gcc volatile.c -o volatile -lpthread
PS C:\Users\DelftStack\Desktop\C> ./volatile
Wenn wir uns die folgende Ausgabe ansehen, laufen die Dinge normalerweise wie erwartet.
Ausgang:
Waiting...
Okay, Let's move on
Jetzt schalten wir die Compiler-Optimierung für denselben Quellcode ein.
PS C:\Users\DelftStack\Desktop\C> gcc -O3 volatile.c -o volatile -lpthread
PS C:\Users\DelftStack\Desktop\C> ./volatile
Es funktioniert nicht wie erwartet, da es die folgende Ausgabe zeigt.
Ausgang:
Waiting...
Der Compiler hat sich die while
-Schleife angesehen und festgestellt, dass die done
-Variable in der while
-Schleife nie aktualisiert wird. Weil der Compiler nicht merkt, dass ein anderer Thread die globale Variable namens done
modifiziert, und das ist der Grund, ändert der Compiler den Code für uns und bricht das Programm ab.
Hier verwenden wir den Qualifier volatile
. Wir aktualisieren den Code und machen die done
-Variable volatile
.
Beispielcode (mit volatile
):
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
volatile bool done = false;
void *tfunc() {
sleep(1);
done = true;
return NULL;
}
int main() {
pthread_t t1;
pthread_create(&t1, NULL, tfunc, NULL);
printf("Waiting...\n");
while (!done) {
}
printf("Okay, Let's move on");
}
Der Code funktioniert auch mit den Optimierungen einwandfrei. Siehe Folgendes.
PS C:\Users\DelftStack\Desktop\C> gcc -O3 volatile.c -o volatile -lpthread
PS C:\Users\DelftStack\Desktop\C> ./volatile
Ausgang:
Waiting...
Okay, Let's move on