1. Warum volatile verwenden?
Das volatile-Schlüsselwort in C/C++ entspricht const und wird zum Modifizieren von Variablen verwendet, typischerweise um eine Speicherbarriere auf Sprachebene zu errichten. Dies ist Bjarne Stroustrups (BS) Erklärung des volatile-Modifizierers in „The C++ Programming Language“:
Ein volatile-Spezifizierer ist ein Hinweis an einen Compiler, dass ein Objekt seinen Wert auf Weisen ändern kann, die von der Sprache nicht spezifiziert sind, sodass aggressive Optimierungen vermieden werden müssen.
Das volatile-Schlüsselwort ist ein Typmodifizierer. Eine damit deklarierte Variable zeigt an, dass sie durch Faktoren geändert werden kann, die dem Compiler unbekannt sind, wie etwa das Betriebssystem, Hardware oder andere Threads. Wenn der Compiler auf eine mit diesem Schlüsselwort deklarierte Variable trifft, optimiert er den Code, der auf die Variable zugreift, nicht mehr und ermöglicht dadurch einen stabilen Zugriff auf spezielle Adressen. Die Syntax für die Deklaration lautet:int volatile vInt;Wenn der Wert einer mit volatile deklarierten Variablen angefordert wird, liest das System die Daten immer erneut aus dem Speicher, in dem sie sich befindet, selbst wenn die vorhergehende Anweisung gerade Daten von dieser Stelle gelesen hat. Außerdem werden die gelesenen Daten sofort gesichert. Zum Beispiel:
volatile int i=10; int a = i; ... // 其他代码,并未明确告诉编译器,对 i 进行过操作 int b = i;
volatile zeigt an, dass i sich jederzeit ändern kann. Jedes Mal, wenn i verwendet wird, muss sein Wert von der Adresse von i gelesen werden. Daher liest der vom Compiler erzeugte Assembler-Code die Daten erneut von der Adresse von i und legt sie in b ab. Der optimierte Ansatz ist jedoch, dass der Compiler, weil er feststellt, dass der Code zwischen den beiden Lesevorgängen von i nicht auf i zugreift, automatisch die zuvor gelesenen Daten in b ablegt, anstatt erneut von i zu lesen. Auf diese Weise können Fehler leicht auftreten, wenn i eine Registervariable ist oder Portdaten repräsentiert. Daher kann volatile einen stabilen Zugriff auf spezielle Adressen gewährleisten. Beachten Sie, dass im VC 6 der allgemeine Debug-Modus keine Codeoptimierung durchführt, sodass die Wirkung dieses Schlüsselworts nicht sichtbar ist. Im Folgenden testen wir durch Einfügen von Assembler-Code die Auswirkung des Vorhandenseins oder Fehlens des Schlüsselworts volatile auf den endgültigen Code des Programms. Geben Sie den folgenden Code ein:
Beispiel
Führen Sie das Programm dann im Debug-Modus aus, und das Ausgabeergebnis ist wie folgt:
i = 10 i = 32
Führen Sie das Programm dann im Release-Modus aus, und das Ausgabeergebnis ist wie folgt:
i = 10 i = 10
Die Ausgabe zeigt deutlich, dass der Compiler im Release-Modus den Code optimiert hat und beim zweiten Mal nicht den korrekten Wert von i ausgegeben hat. Im Folgenden fügen wir das Schlüsselwort volatile zur Deklaration von i hinzu und sehen, was sich ändert:
Beispiel
Führen Sie das Programm jeweils in der Debug- und Release-Version aus; die Ausgabe ist in beiden:
i = 10 i = 32
Dies zeigt, dass das Schlüsselwort volatile seine Rolle gespielt hat. Tatsächlich ist es nicht nur der Ansatz „Manipulation des Stapels über Inline-Assembly“, der vom Compiler nicht erkennbare Variablenänderungen darstellt. Häufiger ist es so: Wenn mehrere Threads gleichzeitig auf gemeinsam genutzte Variablen zugreifen und ein Thread den Wert einer Variablen ändert, wie kann der geänderte Wert für andere Threads sichtbar gemacht werden? Im Allgemeinen wird volatile an den folgenden Stellen verwendet:
- 1) Variablen, die in Interrupt-Service-Routinen geändert werden und von anderen Programmen zur Erkennung verwendet werden, müssen als volatile deklariert werden;
- 2) Flags, die zwischen Tasks in einer Multitasking-Umgebung gemeinsam genutzt werden, sollten als volatile deklariert werden;
- 3) Speicherzugeordnete Hardware-Register benötigen normalerweise ebenfalls den Qualifizierer volatile, da jedes Lesen aus ihnen oder Schreiben in sie eine unterschiedliche Bedeutung haben kann;
2. volatile-Zeiger
Ähnlich wie beim const-Modifizierer – const kennt die Konzepte Zeiger auf const und const-Zeiger – hat volatile entsprechende Konzepte:
Modifizieren des Objekts, auf das der Zeiger zeigt; die Daten sind const oder volatile:
const char* cpch; volatile char* vpch;
Hinweis: Für VC ist dieses Feature nur sicher, wenn es in VC 8 oder höher implementiert ist.
Der Wert des Zeigers selbst – eine Integer-Variable, die eine Adresse repräsentiert – ist const oder volatile:
char* const pchc; char* volatile pchv;
Hinweis:
- (1) Sie können ein nicht-volatile int einem volatile int zuweisen, aber Sie können kein nicht-volatile Objekt einem volatile Objekt zuweisen.
- (2) Neben Basistypen können auch benutzerdefinierte Typen mit dem volatile-Typ modifiziert werden.
- (3) In C++ kann eine Klasse mit einem volatile-Bezeichner nur auf eine Teilmenge ihrer Schnittstelle zugreifen, eine Teilmenge, die vom Implementierer der Klasse kontrolliert wird. Benutzer können nur const_cast verwenden, um vollständigen Zugriff auf die Schnittstelle des Typs zu erhalten. Außerdem breitet sich volatile, wie const, von einer Klasse auf ihre Mitglieder aus.
3. volatile im Multithreading
Einige Variablen werden mit dem Schlüsselwort volatile deklariert. Wenn zwei Threads dieselbe Variable verwenden müssen und der Wert dieser Variable geändert wird, sollte sie mit volatile deklariert werden. Die Aufgabe dieses Schlüsselworts besteht darin, den optimierenden Compiler daran zu hindern, die Variable aus dem Speicher in ein CPU-Register zu laden. Wenn die Variable in ein Register geladen wird, können die beiden Threads möglicherweise jeweils die Variable im Speicher und die Variable in einem Register verwenden, was zu einer fehlerhaften Programmausführung führen kann. volatile bedeutet, dass der Compiler die Variable bei jedem Zugriff darauf immer tatsächlich aus dem Speicher holen soll, anstatt den bereits im Register gespeicherten Wert zu verwenden, wie folgt:
volatile BOOL bStop = FALSE;
(1) In einem Thread:
while( !bStop ) { ... }
bStop = FALSE;
return;
(2) In einem anderen Thread, um die obige Thread-Schleife zu beenden:
bStop = TRUE; while( bStop ); //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。
Dieses Schlüsselwort wird verwendet, um den Speicherort eines Objekts im Speicher statt in einem Register festzulegen. Denn bei gewöhnlichen Objekten kann der Compiler eine Kopie davon in einem Register ablegen, um die Ausführung von Anweisungen zu beschleunigen, zum Beispiel im folgenden Code:
...
int nMyCounter = 0;
for(; nMyCounter<100;nMyCounter++)
{
...
}
...
In diesem Codeabschnitt kann eine Kopie von nMyCounter in einem Register gespeichert werden (in der Schleife werden Tests und Operationen an nMyCounter immer mit dem Wert in diesem Register durchgeführt), aber es gibt einen anderen Codeabschnitt, der eine Operation wie diese ausführt:nMyCounter -= 1;Bei dieser Operation wird die Änderung an nMyCounter am nMyCounter im Speicher vorgenommen, wodurch ein Phänomen entsteht: Die Änderungen an nMyCounter sind nicht synchronisiert.
Originaladresse: https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html