Ich habe einige Artikel und Stack Exchange-Antworten zur Verwendung des volatile
Schlüsselworts gelesen , um zu verhindern, dass der Compiler Optimierungen auf Objekte anwendet, die sich auf vom Compiler nicht feststellbare Weise ändern können.
Wenn ich aus einem ADC lese (nennen wir die Variable adcValue
) und diese Variable als global deklariere, sollte ich volatile
in diesem Fall das Schlüsselwort verwenden ?
Ohne
volatile
Schlüsselwort// Includes #include "adcDriver.h" // Global variables uint16_t adcValue; // Some code void readFromADC(void) { adcValue = readADC(); }
Verwenden Sie das
volatile
Schlüsselwort// Includes #include "adcDriver.h" // Global variables volatile uint16_t adcValue; // Some code void readFromADC(void) { adcValue = readADC(); }
Ich stelle diese Frage, weil ich beim Debuggen keinen Unterschied zwischen beiden Ansätzen feststellen kann, obwohl nach den Best Practices in meinem Fall (eine globale Variable, die sich direkt von der Hardware ändert) die Verwendung volatile
obligatorisch ist.
microcontroller
c
embedded
Pryda
quelle
quelle
if(x==1) x=1;
beim Schreiben für einen nichtflüchtigenx
Wert optimiert werden und kann nicht optimiert werden, wenn erx
flüchtig ist. OTOH Wenn für den Zugriff auf externe Geräte spezielle Anweisungen erforderlich sind, müssen Sie diese hinzufügen (z. B. wenn ein Speicherbereich durchgeschrieben werden muss).Antworten:
Eine Definition von
volatile
volatile
teilt dem Compiler mit, dass sich der Wert der Variablen ändern kann, ohne dass der Compiler davon erfährt. Daher kann der Compiler nicht davon ausgehen, dass sich der Wert nicht geändert hat, nur weil das C-Programm ihn anscheinend nicht geändert hat.Auf der anderen Seite bedeutet dies, dass der Wert der Variablen möglicherweise an einer anderen Stelle benötigt (gelesen) wird, über die der Compiler nichts weiß. Daher muss sichergestellt werden, dass jede Zuweisung zu der Variablen tatsächlich als Schreiboperation ausgeführt wird.
Anwendungsfälle
volatile
wird benötigt wennEffekte von
volatile
Wenn eine Variable deklariert wird,
volatile
muss der Compiler sicherstellen, dass sich jede Zuweisung im Programmcode auf eine tatsächliche Schreiboperation auswirkt und dass jeder eingelesene Programmcode den Wert aus dem (MMAPP-) Speicher liest.Bei nichtflüchtigen Variablen geht der Compiler davon aus, dass er weiß, ob / wann sich der Wert der Variablen ändert, und dass er den Code auf verschiedene Arten optimieren kann.
Zum einen kann der Compiler die Anzahl der Lese- / Schreibvorgänge in den Speicher reduzieren, indem er den Wert in den CPU-Registern beibehält.
Beispiel:
Hier wird der Compiler wahrscheinlich nicht einmal RAM für die
result
Variable zuweisen und die Zwischenwerte niemals irgendwo anders als in einem CPU-Register speichern.Wenn
result
flüchtig, würde jedes Auftretenresult
im C-Code erfordern, dass der Compiler einen Zugriff auf RAM (oder einen E / A-Port) ausführt, was zu einer geringeren Leistung führt.Zweitens kann der Compiler Operationen an nichtflüchtigen Variablen hinsichtlich Leistung und / oder Codegröße neu anordnen. Einfaches Beispiel:
könnte nachbestellt werden
Dies kann eine Assembler-Anweisung speichern, da der Wert
99
nicht zweimal geladen werden muss.Wenn
a
,b
undc
flüchtig wäre , würde der Compiler Anweisungen emittieren , welche die Werte in der exakten Reihenfolge vergeben , wie sie im Programm angegeben.Das andere klassische Beispiel sieht so aus:
Wenn in diesem Fall
signal
war nichtvolatile
, würde der Compiler ‚denken‘ , dasswhile( signal == 0 )
eine unendliche Schleife sein kann (weilsignal
nie von Code geändert wird innerhalb der Schleife ) und könnte die äquivalent erzeugenRücksichtsvoller Umgang mit
volatile
WertenWie oben erwähnt, kann eine
volatile
Variable eine Leistungsstrafe nach sich ziehen, wenn auf sie häufiger zugegriffen wird als tatsächlich erforderlich. Um dieses Problem zu beheben, können Sie den Wert "nichtflüchtig" machen, indem Sie ihn einer nichtflüchtigen Variablen zuweisen, zDies kann insbesondere bei ISRs von Vorteil sein, bei denen Sie so schnell wie möglich nicht mehrmals auf dieselbe Hardware oder denselben Speicher zugreifen möchten, wenn Sie wissen, dass dies nicht erforderlich ist, da sich der Wert nicht ändert, während Ihr ISR ausgeführt wird. Dies ist häufig der Fall, wenn der ISR der 'Produzent' von Werten für die Variable ist, wie
sysTickCount
im obigen Beispiel. Bei einem AVR wäre es besonders schmerzhaft, wenn die Funktion fünf- oder sechsmal statt nur zweimaldoSysTick()
auf dieselben vier Bytes im Speicher zugreifen würde (vier Anweisungen = 8 CPU-Zyklen pro ZugriffsysTickCount
), da der Programmierer weiß, dass dies nicht der Fall ist von einem anderen Code geändert werden, während sein / ihrdoSysTick()
läuft.Mit diesem Trick machen Sie genau dasselbe, was der Compiler für nichtflüchtige Variablen macht, dh, Sie lesen sie nur dann aus dem Speicher, wenn sie benötigt werden, behalten den Wert einige Zeit in einem Register und schreiben nur dann in den Speicher zurück, wenn dies erforderlich ist ; Aber dieses Mal wissen Sie besser als der Compiler, ob / wann Lese- / Schreibvorgänge erforderlich sind. Sie entlasten den Compiler von dieser Optimierungsaufgabe und erledigen dies selbst.
Einschränkungen von
volatile
Nichtatomarer Zugang
volatile
bietet keinen atomaren Zugriff auf Variablen mit mehreren Wörtern. In diesen Fällen müssen Sie zusätzlich zur Verwendung einen gegenseitigen Ausschluss auf andere Weise vorsehenvolatile
. Auf dem AVR können SieATOMIC_BLOCK
aus<util/atomic.h>
oder einfachecli(); ... sei();
Anrufe tätigen. Die jeweiligen Makros fungieren auch als Speicherbarriere, was bei der Reihenfolge der Zugriffe wichtig ist:Ausführungsreihenfolge
volatile
legt strikte Ausführungsreihenfolge nur in Bezug auf andere flüchtige Variablen fest. Dies bedeutet zum Beispiel, dasswird garantiert zuerst 1 zuweisen
i
und dann 2 zuweisenj
. Es kann jedoch nicht garantiert werden, dassa
die Zuweisung zwischenzeitlich erfolgt. Der Compiler kann diese Zuweisung vor oder nach dem Code-Snippet vornehmen, und zwar grundsätzlich jederzeit bis zum ersten (sichtbaren) Lesen vona
.Ohne die Speicherbarriere der oben genannten Makros könnte der Compiler übersetzen
zu
oder
(Der Vollständigkeit halber muss ich sagen, dass Speicherbarrieren, wie sie in den sei / cli-Makros impliziert sind, die Verwendung von möglicherweise sogar überflüssig machen
volatile
, wenn alle Zugriffe mit diesen Barrieren eingeklammert sind.)quelle
An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects.
Mehr Leute sollten es lesen.cli
/sei
eine zu schwere Lösung ist, wenn Ihr einziges Ziel darin besteht, eine Speicherbarriere zu erreichen, keine Interrupts zu verhindern. Diese Makros generieren aktuellecli
/sei
Anweisungen und zusätzlich den Speicher für Unrat, und es ist dieses Unrat, das zur Barriere führt. Um nur eine Speichersperre zu haben, ohne Interrupts zu deaktivieren, können Sie Ihr eigenes Makro mit dem Body wie__asm__ __volatile__("":::"memory")
(dh leerer Assembler-Code mit Memory Clobber) definieren.volatile
wird, steht ein Sequenzpunkt, und alles, was danach folgt, muss "sequenziert werden". Das heißt, dieser Ausdruck ist eine Art Gedächtnisbarriere. Compiler-Hersteller haben sich entschieden, alle Arten von Mythen zu verbreiten, um dem Programmierer die Verantwortung für Speicherbarrieren aufzuerlegen, aber dies verstößt gegen die Regeln der "abstrakten Maschine".volatile data_t data = {0}; set_mmio(&data); while (!data.ready);
.Das Schlüsselwort volatile teilt dem Compiler mit, dass der Zugriff auf die Variable einen beobachtbaren Effekt hat. Das heißt, jedes Mal, wenn Ihr Quellcode die Variable verwendet, MUSS der Compiler einen Zugriff auf die Variable erstellen. Sei das ein Lese- oder Schreibzugriff.
Dies hat zur Folge, dass jede Änderung der Variablen außerhalb des normalen Codeflusses auch vom Code beobachtet wird. ZB wenn ein Interrupt-Handler den Wert ändert. Oder wenn die Variable tatsächlich ein Hardware-Register ist, das sich von selbst ändert.
Dieser große Vorteil ist auch der Nachteil. Jeder einzelne Zugriff auf die Variable durchläuft die Variable, und der Wert wird niemals in einem Register gespeichert, um den Zugriff für einen beliebigen Zeitraum zu beschleunigen. Das bedeutet, dass eine flüchtige Variable langsam ist. Größenordnungen langsamer. Verwenden Sie daher volatile nur dort, wo es tatsächlich erforderlich ist.
In Ihrem Fall wird die globale Variable, soweit Sie den Code angezeigt haben, nur geändert, wenn Sie sie selbst aktualisieren
adcValue = readADC();
. Der Compiler weiß, wann dies geschieht, und speichert den Wert von adcValue niemals in einem Register über etwas, das diereadFromADC()
Funktion möglicherweise aufruft . Oder irgendeine Funktion, von der es nichts weiß. Oder irgendetwas, das Zeiger manipuliert, die auf etwas zeigen könnten,adcValue
und so weiter. Es ist wirklich keine Volatilität erforderlich, da sich die Variable niemals auf unvorhersehbare Weise ändert.quelle
volatile
alles verwenden, nur weil , aber Sie sollten auch nicht davor zurückschrecken, wenn Sie der Meinung sind, dass dies aufgrund präventiver Leistungsprobleme gerechtfertigt ist.Das flüchtige Schlüsselwort wird hauptsächlich in eingebetteten C-Anwendungen verwendet, um eine globale Variable zu markieren , in die in einem Interrupt-Handler geschrieben wird. Dies ist in diesem Fall sicherlich nicht optional.
Ohne dies kann der Compiler nicht nachweisen, dass der Wert jemals nach der Initialisierung geschrieben wurde, da er nicht nachweisen kann, dass der Interrupt-Handler jemals aufgerufen wurde. Daher denkt es, dass es die Variable aus dem Dasein heraus optimieren kann.
quelle
Es gibt zwei Fälle, in denen Sie
volatile
in eingebetteten Systemen verwenden müssen.Beim Lesen aus einem Hardware-Register.
Das heißt, das speicherabgebildete Register selbst ist Teil der Hardware-Peripherie innerhalb der MCU. Es wird wahrscheinlich einen kryptischen Namen wie "ADC0DR" haben. Dieses Register muss im C-Code entweder über eine vom Werkzeughersteller bereitgestellte Registerkarte oder von Ihnen selbst definiert werden. Um es selbst zu machen, würden Sie (unter der Annahme eines 16-Bit-Registers) Folgendes tun:
Dabei ist 0x1234 die Adresse, auf die die MCU das Register abgebildet hat. Da dies
volatile
bereits Teil des oben genannten Makros ist, ist jeder Zugriff auf dieses Makro flüchtig. Also dieser Code ist in Ordnung:Wenn eine Variable zwischen einem ISR und dem zugehörigen Code unter Verwendung des Ergebnisses des ISR geteilt wird.
Wenn Sie so etwas haben:
Dann könnte der Compiler denken: "adc_data ist immer 0, weil es nirgendwo aktualisiert wird. Und diese ADC0_interrupt () - Funktion wird niemals aufgerufen, so dass die Variable nicht geändert werden kann". Der Compiler merkt normalerweise nicht, dass Interrupts von der Hardware und nicht von der Software aufgerufen werden. Daher entfernt der Compiler den Code,
if(adc_data > 0){ do_stuff(adc_data); }
da er der Meinung ist, dass er niemals wahr sein kann, was zu einem sehr seltsamen und schwer zu debuggenden Fehler führt.Durch die Deklaration
adc_data
volatile
darf der Compiler keine solchen Annahmen treffen und den Zugriff auf die Variable nicht optimieren.Wichtige Notizen:
Ein ISR muss immer im Hardwaretreiber deklariert werden. In diesem Fall sollte sich der ADC ISR im ADC-Treiber befinden. Niemand anderes als der Fahrer sollte mit dem ISR kommunizieren - alles andere ist Spaghetti-Programmierung.
Wenn C schreiben, die gesamte Kommunikation zwischen einem ISR und dem Hintergrundprogramm muss gegen Rennbedingungen geschützt werden. Immer , jedes Mal, keine Ausnahmen. Die Größe des MCU-Datenbusses spielt keine Rolle, denn selbst wenn Sie eine einzelne 8-Bit-Kopie in C erstellen, kann die Sprache keine atomaren Operationen garantieren. Nur wenn Sie die C11-Funktion verwenden
_Atomic
. Wenn diese Funktion nicht verfügbar ist, müssen Sie eine Art Semaphor verwenden oder den Interrupt beim Lesen usw. deaktivieren. Inline-Assembler ist eine weitere Option.volatile
garantiert keine Atomizität.Folgendes kann passieren: - Wert vom Stapel in das Register
laden. - Es tritt ein
Interrupt auf. - Wert aus dem Register verwenden
Und dann spielt es keine Rolle, ob der Teil "use value" eine einzelne Anweisung für sich ist. Leider ist sich ein erheblicher Teil aller Programmierer für eingebettete Systeme dessen bewusst, so dass es wahrscheinlich der häufigste Fehler ist, der jemals bei eingebetteten Systemen aufgetreten ist. Immer wieder sporadisch, schwer zu provozieren, schwer zu finden.
Ein Beispiel für einen korrekt geschriebenen ADC-Treiber sieht folgendermaßen aus (vorausgesetzt, C11
_Atomic
ist nicht verfügbar):adc.h
adc.c
Dieser Code geht davon aus, dass ein Interrupt an sich nicht unterbrochen werden kann. Auf solchen Systemen kann ein einfacher Boolescher Wert als Semaphor fungieren und muss nicht atomar sein, da es keinen Schaden gibt, wenn der Interrupt vor dem Setzen des Booleschen Werts auftritt. Der Nachteil der oben beschriebenen vereinfachten Methode ist, dass ADC-Lesevorgänge verworfen werden, wenn Rennbedingungen auftreten, und stattdessen der vorherige Wert verwendet wird. Dies kann auch vermieden werden, aber dann wird der Code komplexer.
Hier
volatile
schützt vor Optimierungsfehlern. Es hat nichts mit den Daten zu tun, die aus einem Hardwareregister stammen, nur dass die Daten mit einem ISR geteilt werden.static
schützt vor Spaghetti-Programmierung und Namespace-Verschmutzung, indem die Variable lokal für den Treiber festgelegt wird. (Dies ist in Single-Core- und Single-Thread-Anwendungen in Ordnung, jedoch nicht in Multi-Thread-Anwendungen.)quelle
semaphore
sollte auf jeden fall seinvolatile
! Tatsächlich ist dies der grundlegendste Anwendungsfall, der Folgendes erfordertvolatile
: Signalisieren Sie etwas von einem Ausführungskontext in einen anderen. - In Ihrem Beispiel könnte der Compiler einfach weglassen,semaphore = true;
weil er "sieht", dass sein Wert niemals gelesen wird, bevor er von überschrieben wirdsemaphore = false;
.In den in der Frage vorgestellten Codeausschnitten gibt es noch keinen Grund, volatile zu verwenden. Es ist irrelevant, dass der Wert von
adcValue
aus einem ADC stammt. UndadcValue
global zu sein sollte Sie misstrauisch machen, obadcValue
es volatil sein sollte, aber es ist kein Grund für sich.Global zu sein ist ein Hinweis, weil es die Möglichkeit eröffnet,
adcValue
von mehr als einem Programmkontext aus darauf zuzugreifen. Ein Programmkontext enthält einen Interrupt-Handler und eine RTOS-Task. Wenn die globale Variable durch einen Kontext geändert wird, können die anderen Programmkontexte nicht davon ausgehen, dass sie den Wert aus einem vorherigen Zugriff kennen. Jeder Kontext muss den Variablenwert jedes Mal neu lesen, wenn er verwendet wird, da der Wert möglicherweise in einem anderen Programmkontext geändert wurde. Einem Programmkontext ist nicht bekannt, wann ein Interrupt- oder Taskwechsel auftritt. Daher muss davon ausgegangen werden, dass sich globale Variablen, die von mehreren Kontexten verwendet werden, aufgrund eines möglichen Kontextwechsels zwischen Zugriffen auf die Variable ändern können. Dafür ist die volatile-Deklaration gedacht. Es teilt dem Compiler mit, dass sich diese Variable außerhalb Ihres Kontexts ändern kann. Lesen Sie sie daher bei jedem Zugriff und gehen Sie nicht davon aus, dass Sie den Wert bereits kennen.Wenn die Variable einer Hardwareadresse im Speicher zugeordnet ist, sind die von der Hardware vorgenommenen Änderungen tatsächlich ein anderer Kontext außerhalb des Kontexts Ihres Programms. Memory-Mapped ist also auch ein Hinweis. Wenn Ihre
readADC()
Funktion beispielsweise auf einen Speicherzuordnungswert zugreift, um den ADC-Wert abzurufen, sollte diese Speicherzuordnungsvariable möglicherweise flüchtig sein.Zurück zu Ihrer Frage: Wenn Ihr Code mehr enthält und
adcValue
von einem anderen Code aufgerufen wird, der in einem anderen Kontext ausgeführt wird,adcValue
sollte er volatil sein.quelle
Nur weil der Wert von einem Hardware-ADC-Register kommt, heißt das nicht, dass er von der Hardware "direkt" geändert wird.
In Ihrem Beispiel rufen Sie einfach readADC () auf, was einen ADC-Registerwert zurückgibt. Dies ist in Bezug auf den Compiler in Ordnung, da er weiß, dass adcValue an diesem Punkt einen neuen Wert zugewiesen wird.
Anders wäre es, wenn Sie eine ADC-Interruptroutine verwenden, um den neuen Wert zuzuweisen, der aufgerufen wird, wenn ein neuer ADC-Wert bereit ist. In diesem Fall hat der Compiler keine Ahnung, wann der entsprechende ISR aufgerufen wird, und entscheidet möglicherweise, dass auf adcValue nicht auf diese Weise zugegriffen wird. Hier würde volatile helfen.
quelle
Das Verhalten des
volatile
Arguments hängt weitgehend von Ihrem Code, dem Compiler und der durchgeführten Optimierung ab.Es gibt zwei Anwendungsfälle, die ich persönlich benutze
volatile
:Wenn es eine Variable gibt, die ich mit dem Debugger betrachten möchte, die jedoch vom Compiler optimiert wurde (dh, sie wurde gelöscht, weil festgestellt wurde, dass diese Variable nicht erforderlich ist),
volatile
wird der Compiler durch Hinzufügen gezwungen, sie beizubehalten kann beim Debuggen gesehen werden.Wenn sich die Variable möglicherweise "außerhalb des Codes" ändert, ist dies normalerweise der Fall, wenn Hardware darauf zugreift oder wenn Sie die Variable direkt einer Adresse zuordnen.
In embedded gibt es auch manchmal einige Fehler in den Compilern, die eine Optimierung durchführen, die eigentlich nicht funktioniert, und manchmal
volatile
die Probleme lösen können.Wenn Sie Ihre Variable global deklariert haben, wird sie wahrscheinlich nicht optimiert, solange die Variable im Code verwendet wird, zumindest wenn sie geschrieben und gelesen wird.
Beispiel:
In diesem Fall wird die Variable wahrscheinlich für printf ("% i", 1) optimiert.
wird nicht optimiert
Noch einer:
In diesem Fall optimiert der Compiler möglicherweise (wenn Sie die Geschwindigkeit optimieren) und verwirft die Variable
Für Ihren Anwendungsfall hängt "es möglicherweise davon ab", wie der Rest Ihres Codes an
adcValue
anderer Stelle verwendet wird und welche Compilerversion / Optimierungseinstellungen Sie verwenden.Manchmal kann es ärgerlich sein, einen Code zu haben, der ohne Optimierung arbeitet, aber einmal optimiert bricht.
Dies könnte optimiert werden für printf ("% i", readADC ());
-
Diese werden wahrscheinlich nicht optimiert, aber Sie wissen nie, "wie gut der Compiler ist" und können sich mit den Compiler-Parametern ändern. In der Regel werden Compiler mit guter Optimierung lizenziert.
quelle
volatile
der Compiler gezwungen, eine Variable im RAM zu speichern und diesen RAM zu aktualisieren, sobald der Variablen ein Wert zugewiesen wird. Meistens 'löscht' der Compiler keine Variablen, da normalerweise keine Zuweisungen ohne Wirkung geschrieben werden. Es kann jedoch vorkommen, dass die Variable in einem CPU-Register bleibt und später oder nie in den Arbeitsspeicher geschrieben wird. Debugger finden häufig nicht das CPU-Register, in dem sich die Variable befindet, und können daher ihren Wert nicht anzeigen.Viele technische Erklärungen, aber ich möchte mich auf die praktische Anwendung konzentrieren.
Das
volatile
Schlüsselwort zwingt den Compiler, den Wert der Variablen bei jeder Verwendung aus dem Speicher zu lesen oder zu schreiben. Normalerweise versucht der Compiler zu optimieren, führt jedoch keine unnötigen Lese- und Schreibvorgänge durch, z. B. indem der Wert in einem CPU-Register gespeichert wird, anstatt jedes Mal auf den Speicher zuzugreifen.Dies hat zwei Hauptverwendungen im eingebetteten Code. Erstens wird es für Hardware-Register verwendet. Hardware-Register können sich ändern, z. B. kann ein ADC-Ergebnisregister vom ADC-Peripheriegerät geschrieben werden. Hardware-Register können auch Aktionen ausführen, wenn auf sie zugegriffen wird. Ein gängiges Beispiel ist das Datenregister eines UART, das beim Lesen häufig Interrupt-Flags löscht.
Der Compiler würde normalerweise versuchen, wiederholte Lese- und Schreibvorgänge des Registers unter der Annahme zu optimieren, dass sich der Wert niemals ändert, sodass nicht ständig darauf zugegriffen werden muss. Das
volatile
Schlüsselwort erzwingt jedoch, dass jedes Mal eine Leseoperation ausgeführt wird.Die zweite häufige Verwendung betrifft Variablen, die sowohl vom Interrupt-Code als auch vom Nicht-Interrupt-Code verwendet werden. Interrupts werden nicht direkt aufgerufen, so dass der Compiler nicht bestimmen kann, wann sie ausgeführt werden. Daher wird davon ausgegangen, dass keine Zugriffe innerhalb des Interrupts erfolgen. Da das
volatile
Schlüsselwort den Compiler zwingt, jedes Mal auf die Variable zuzugreifen, wird diese Annahme entfernt.Es ist wichtig zu beachten, dass das
volatile
Schlüsselwort keine vollständige Lösung für diese Probleme darstellt, und es muss sorgfältig vorgegangen werden, um sie zu vermeiden. Beispielsweise erfordert eine 16-Bit-Variable auf einem 8-Bit-System zwei Speicherzugriffe zum Lesen oder Schreiben, und selbst wenn der Compiler gezwungen ist, diese Zugriffe auszuführen, treten sie sequentiell auf, und es ist möglich, dass die Hardware beim ersten Zugriff oder beim ersten Zugriff handelt ein Interrupt zwischen den beiden auftreten.quelle
Wenn kein
volatile
Qualifikationsmerkmal vorhanden ist, kann der Wert eines Objekts in bestimmten Teilen des Codes an mehr als einer Stelle gespeichert werden. Betrachten Sie zum Beispiel Folgendes:In den frühen Tagen von C hätte ein Compiler die Anweisung verarbeitet
über die schritte:
Anspruchsvollere Compiler werden jedoch erkennen, dass, wenn der Wert von "foo" während der Schleife in einem Register gehalten wird, er nur einmal vor der Schleife geladen und danach einmal gespeichert werden muss. Während der Schleife bedeutet dies jedoch, dass der Wert von "foo" an zwei Stellen gespeichert wird - im globalen Speicher und im Register. Dies ist kein Problem, wenn der Compiler alle Arten des Zugriffs auf "foo" in der Schleife sehen kann, aber Probleme verursachen kann, wenn auf den Wert von "foo" in einem Mechanismus zugegriffen wird, über den der Compiler nichts weiß ( wie ein Interrupt-Handler).
Möglicherweise konnten die Autoren des Standards ein neues Qualifikationsmerkmal hinzufügen, das den Compiler explizit auffordert, solche Optimierungen vorzunehmen, und sagen, dass die altmodische Semantik in ihrer Abwesenheit angewendet würde, aber in Fällen, in denen die Optimierungen von großem Nutzen sind, übertrifft die Anzahl Diejenigen, bei denen es problematisch wäre, so dass der Standard den Compilern stattdessen die Annahme erlaubt, dass solche Optimierungen sicher sind, wenn keine Beweise dafür vorliegen, dass dies nicht der Fall ist. Der Zweck des
volatile
Schlüsselworts ist es, solche Beweise zu liefern.Ein paar Streitpunkte zwischen einigen Compiler-Autoren und Programmierern treten in folgenden Situationen auf:
In der Vergangenheit würden die meisten Compiler entweder die Möglichkeit einräumen, dass das Schreiben eines
volatile
Speicherorts willkürliche Nebenwirkungen hervorruft, und das Zwischenspeichern von Werten in Registern in einem solchen Speicher vermeiden, oder sie würden das Zwischenspeichern von Werten in Registern über Aufrufe von Funktionen hinweg unterlassen, die sind nicht als "Inline" qualifiziert und würde daher 0x1234 schreibenoutput_buffer[0]
, die Daten ausgeben, warten, bis der Vorgang abgeschlossen ist, dann 0x2345 schreibenoutput_buffer[0]
und fortfahren. Der Standard befasst sich nicht erfordern Implementierungen den Akt der Speicherung der Adresse der behandelnoutput_buffer
in einvolatile
-qualifizierter Zeiger als Zeichen dafür, dass etwas mit ihm geschehen könnte, bedeutet jedoch, dass der Compiler dies nicht versteht, da die Autoren der Ansicht waren, dass Compiler, die für verschiedene Plattformen und Zwecke vorgesehen sind, erkennen würden, dass dies diesen Zwecken auf diesen Plattformen dienen würde ohne dass es gesagt werden muss. Folglich gehen einige "clevere" Compiler wie gcc und clang davon aus, dass, obwohl die Adresse vonoutput_buffer
in einen flüchtig qualifizierten Zeiger zwischen den beiden Speichern von geschriebenoutput_buffer[0]
wird, dies keinen Grund zur Annahme gibt, dass der Wert, der in diesem Objekt von at enthalten ist, von irgendetwas abhängt diese Zeit.Während Zeiger, die direkt aus Ganzzahlen erzeugt werden, selten für andere Zwecke verwendet werden, als um Dinge auf eine Weise zu manipulieren, die Compiler wahrscheinlich nicht verstehen, verlangt der Standard wiederum nicht, dass Compiler solche Zugriffe wie behandeln
volatile
. Folglich kann das erste Schreiben*((unsigned short*)0xC0001234)
von "cleveren" Compilern wie gcc und clang weggelassen werden, da die Betreuer solcher Compiler eher behaupten würden, dass Code, der es versäumt, solche Dinge alsvolatile
"kaputt" zu qualifizieren, als zu erkennen, dass die Kompatibilität mit solchem Code nützlich ist . Viele vom Hersteller bereitgestellte Header-Dateien lassenvolatile
Qualifikationsmerkmale aus, und ein Compiler, der mit den vom Hersteller bereitgestellten Header-Dateien kompatibel ist, ist nützlicher als eine andere.quelle