Darf ein Compiler eine lokale flüchtige Variable optimieren?

79

Darf der Compiler dies optimieren (gemäß C ++ 17-Standard):

int fn() {
    volatile int x = 0;
    return x;
}

dazu?

int fn() {
    return 0;
}

Wenn ja, warum? Wenn nicht, warum nicht?


Hier einige Überlegungen zu diesem Thema: Aktuelle Compiler werden fn()als lokale Variable kompiliert , die auf den Stapel gelegt wird, und geben sie dann zurück. Auf x86-64 erstellt gcc beispielsweise Folgendes:

mov    DWORD PTR [rsp-0x4],0x0 // this is x
mov    eax,DWORD PTR [rsp-0x4] // eax is the return register
ret    

Soweit ich weiß, sagt der Standard nicht, dass eine lokale flüchtige Variable auf den Stapel gelegt werden sollte. Diese Version wäre also genauso gut:

mov    edx,0x0 // this is x
mov    eax,edx // eax is the return
ret    

Hier edxspeichert x. Aber warum jetzt hier aufhören? Da edxund eaxbeide Null sind, können wir einfach sagen:

xor    eax,eax // eax is the return, and x as well
ret    

Und wir haben fn()auf die optimierte Version umgestellt. Ist diese Transformation gültig? Wenn nicht, welcher Schritt ist ungültig?

geza
quelle
1
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
@philipxy: Es geht nicht um "was produzieren könnte". Es geht darum, ob die Transformation erlaubt ist. Denn wenn es nicht erlaubt ist, darf es die transformierte Version nicht erzeugen.
Geza
Der Standard definiert für ein Programm eine Folge von Zugriffen auf flüchtige Stoffe und andere beobachtbare Elemente, die eine Implementierung berücksichtigen muss. Der Zugang zu einem volatilen Mittel ist jedoch implementierungsdefiniert. Es ist also sinnlos zu fragen, was eine Implementierung produzieren könnte - sie produziert, was sie produzieren soll. Wenn Sie das Implementierungsverhalten beschreiben, suchen Sie möglicherweise ein anderes, das Sie bevorzugen. Aber du brauchst einen, um zu beginnen. Vielleicht interessieren Sie sich tatsächlich für die beobachtbaren Regeln des Standards, da die Codegenerierung irrelevant ist, außer dass Sie die Regeln des Standards und eine Implementierung erfüllen müssen.
Philipip
1
@philipxy: Ich werde meine Frage klären, dass es um den Standard geht. Dies wird normalerweise durch diese Art von Fragen impliziert. Mich interessiert, was der Standard sagt.
Geza

Antworten:

63

Nein. Der Zugriff auf volatileObjekte wird als beobachtbares Verhalten angesehen, genau wie E / A, ohne besondere Unterscheidung zwischen Einheimischen und Globalen.

Die geringsten Anforderungen an eine konforme Implementierung sind:

  • Der Zugriff auf volatileObjekte wird streng nach den Regeln der abstrakten Maschine bewertet.

[...]

Diese werden zusammen als beobachtbares Verhalten des Programms bezeichnet.

N3690, [intro.execution], ¶8

Wie genau dies beobachtbar ist, liegt außerhalb des Geltungsbereichs des Standards und fällt direkt in implementierungsspezifisches Gebiet, genau wie E / A und Zugriff auf globale volatileObjekte. volatilebedeutet "du denkst, du weißt alles, was hier vor sich geht, aber es ist nicht so; vertrau mir und mach dieses Zeug, ohne zu schlau zu sein, weil ich in deinem Programm meine geheimen Sachen mit deinen Bytes mache". Dies wird tatsächlich unter [dcl.type.cv] ¶7 erklärt:

[Hinweis: volatileDies ist ein Hinweis auf die Implementierung, um eine aggressive Optimierung des Objekts zu vermeiden, da der Wert des Objekts durch Mittel geändert werden kann, die von einer Implementierung nicht erkannt werden können. Darüber hinaus kann bei einigen Implementierungen flüchtig darauf hinweisen, dass spezielle Hardwareanweisungen erforderlich sind, um auf das Objekt zuzugreifen. Siehe 1.9 für eine detaillierte Semantik. Im Allgemeinen soll die Semantik von flüchtig in C ++ dieselbe sein wie in C. - Endnote]

Matteo Italia
quelle
2
Da dies die am besten bewertete Frage ist und die Frage durch Bearbeiten erweitert wurde, wäre es schön, diese Antwort bearbeiten zu lassen, um die neuen Optimierungsbeispiele zu diskutieren.
Hyde
Richtig ist "Ja". Diese Antwort unterscheidet abstrakte Maschinenbeobachtungsobjekte nicht klar vom generierten Code. Letzteres ist implementierungsdefiniert. Zum Beispiel wird für die Verwendung mit einem bestimmten Debugger garantiert, dass sich ein flüchtiges Objekt im Speicher und / oder im Register befindet. zB typischerweise unter einer relevanten Zielarchitektur werden Schreib- und / oder Lesevorgänge für flüchtige Objekte an pragma-spezifizierten speziellen Speicherstellen garantiert. Die Implementierung definiert, wie Zugriffe im Code wiedergegeben werden. Es entscheidet, wie und wann Objekte "durch Mittel geändert werden können, die von einer Implementierung nicht erkannt werden können". (Siehe meine Kommentare zu der Frage.)
philipxy
12

Diese Schleife kann durch die Als-ob-Regel optimiert werden, da sie kein beobachtbares Verhalten aufweist:

for (unsigned i = 0; i < n; ++i) { bool looped = true; }

Dieser kann nicht:

for (unsigned i = 0; i < n; ++i) { volatile bool looped = true; }

Die zweite Schleife führt bei jeder Iteration etwas aus, was bedeutet, dass die Schleife O (n) Zeit benötigt. Ich habe keine Ahnung, was die Konstante ist, aber ich kann sie messen und habe dann die Möglichkeit, eine (mehr oder weniger) bekannte Zeit lang zu schleifen.

Ich kann das tun, weil der Standard besagt, dass der Zugriff auf flüchtige Stoffe in der richtigen Reihenfolge erfolgen muss. Wenn ein Compiler entscheiden würde, dass in diesem Fall der Standard nicht gilt, hätte ich wahrscheinlich das Recht, einen Fehlerbericht einzureichen.

Wenn der Compiler sich dafür entscheidet, loopedein Register zu erstellen, habe ich vermutlich kein gutes Argument dagegen. Der Wert dieses Registers muss jedoch für jede Schleifeniteration auf 1 gesetzt werden.

Rici
quelle
Wollen Sie damit sagen, dass die endgültige xor ax, ax(wo axals solche angesehen wird volatile x) Version in der Frage gültig oder ungültig ist? IOW, wie lautet Ihre Antwort auf die Frage?
Hyde
@hyde: Die Frage, wie ich sie las, war "Kann die Variable eliminiert werden" und meine Antwort ist "Nein". Für die spezifische x86-Implementierung, die die Frage aufwirft, ob das Volatile in ein Register aufgenommen werden kann, bin ich mir nicht ganz sicher. Selbst wenn es auf xor ax, axdiesen Opcode reduziert wird, kann er nicht entfernt werden, selbst wenn er nutzlos aussieht, und er kann auch nicht zusammengeführt werden. In meinem Schleifenbeispiel müsste der kompilierte Code xor ax, axn-mal ausgeführt werden, um die beobachtbare Verhaltensregel zu erfüllen. Hoffentlich beantwortet die Bearbeitung Ihre Frage.
Rici
Ja, die Frage wurde durch die Bearbeitung ziemlich erweitert, aber da Sie nach der Bearbeitung geantwortet haben, dachte ich, diese Antwort sollte den neuen Teil abdecken ...
Hyde
2
@hyde: Tatsächlich verwende ich auf diese Weise flüchtige Stoffe in Benchmarks, um zu vermeiden, dass der Compiler eine Schleife optimiert, die sonst nichts bewirkt. Also ich hoffe wirklich, dass ich damit Recht habe: =)
Rici
Der Standard besagt, dass Operationen an volatileObjekten an und für sich eine Art Nebeneffekt sind. Eine Implementierung könnte ihre Semantik so definieren, dass sie keine tatsächlichen CPU-Anweisungen generieren müsste. Eine Schleife, die auf ein flüchtig qualifiziertes Objekt zugreift, hat jedoch Nebenwirkungen und kann daher nicht entfernt werden.
Supercat
10

Ich widerspreche der Mehrheitsmeinung, trotz des vollständigen Verständnisses, volatiledas beobachtbare E / A bedeutet.

Wenn Sie diesen Code haben:

{
    volatile int x;
    x = 0;
}

Ich glaube, der Compiler kann es unter der Als-ob-Regel optimieren , vorausgesetzt, dass:

  1. Die volatileVariable wird ansonsten nicht extern über zB Zeiger sichtbar gemacht (was hier offensichtlich kein Problem darstellt, da es im gegebenen Bereich keine solche gibt)

  2. Der Compiler bietet Ihnen keinen Mechanismus für den externen Zugriff darauf volatile

Das Grundprinzip ist einfach, dass Sie den Unterschied aufgrund von Kriterium 2 sowieso nicht beobachten konnten.

In Ihrem Compiler ist Kriterium 2 jedoch möglicherweise nicht erfüllt ! Der Compiler versucht möglicherweise , Ihnen zusätzliche Garantien für die Beobachtung von volatileVariablen von "außen" zu geben, z. B. durch Analyse des Stapels. In solchen Situationen das Verhalten wirklich ist zu beobachten, so dass es nicht weg optimiert werden kann.

Die Frage ist nun, unterscheidet sich der folgende Code von dem oben genannten?

{
    volatile int x = 0;
}

Ich glaube, ich habe in Visual C ++ ein anderes Verhalten in Bezug auf die Optimierung beobachtet, bin mir aber nicht ganz sicher, aus welchen Gründen. Kann es sein, dass die Initialisierung nicht als "Zugriff" zählt? Ich bin mir nicht sicher. Dies kann eine separate Frage wert sein, wenn Sie interessiert sind, aber ansonsten glaube ich, dass die Antwort so ist, wie ich es oben erklärt habe.

user541686
quelle
6

Theoretisch könnte ein Interrupt-Handler

  • Überprüfen Sie, ob die Absenderadresse in die fn()Funktion fällt . Es kann über Instrumentierung oder angehängte Debug-Informationen auf die Symboltabelle oder die Quellzeilennummern zugreifen.
  • Ändern Sie dann den Wert von x, der mit einem vorhersagbaren Versatz vom Stapelzeiger gespeichert wird.

… Damit die fn()Rückgabe einen Wert ungleich Null ergibt.

folgte Monica nach Codidact
quelle
1
Oder Sie können dies einfacher mit einem Debugger tun, indem Sie einen Haltepunkt in setzen fn(). Die Verwendung von volatileerzeugt Code-Gen, das dem gcc -O0für diese Variable entspricht: Überlaufen / Neuladen zwischen jeder C-Anweisung. ( -O0Kann immer noch mehrere Zugriffe in einer Anweisung kombinieren, ohne die Debugger-Konsistenz volatile
Peter Cordes
Oder einfacher mit einem Debugger :) Aber welcher Standard besagt, dass eine Variable beobachtbar sein muss? Ich meine, eine Implementierung kann wählen, dass sie beobachtbar sein muss. Ein anderer kann sagen, es ist nicht beobachtbar. Verstößt letzterer gegen den Standard? Vielleicht nicht. Es ist nicht in der Norm festgelegt, wie eine lokale flüchtige Variable überhaupt beobachtet werden kann.
Geza
Was bedeutet es überhaupt "beobachtbar"? Sollte es auf einen Stapel gelegt werden? Was ist, wenn ein Register hält x? Was ist, wenn auf x86-64 xor rax, raxdie Null enthalten ist (ich meine, das Rückgabewertregister gilt x), was natürlich von einem Debugger leicht beobachtet / geändert werden kann (dh Debug-Symbolinformationen, xdie in gespeichert sind rax). Verstößt dies gegen den Standard?
Geza
2
−1 Jeder Anruf an fn()kann eingebunden werden. Mit MSVC 2017 und dem Standard-Release-Modus ist dies der Fall. Es gibt dann kein "innerhalb der fn()Funktion". Unabhängig davon, da es sich bei der Variablen um eine automatische Speicherung handelt, gibt es keinen „vorhersehbaren Versatz“.
Prost und hth. - Alf
1
0 @berendi: Ja, da hast du recht, und da habe ich mich geirrt. Entschuldigung, diesbezüglich ein schlechter Morgen für mich (zweimal falsch). Trotzdem ist es IMO wertlos zu argumentieren, wie der Compiler den Zugriff über andere Software unterstützen kann, weil er dies unabhängig davon tun kann volatileund weil er volatilenicht gezwungen ist, diese Unterstützung bereitzustellen. Und so entferne ich die Ablehnung (ich habe mich geirrt), aber ich stimme nicht zu, weil ich denke, dass diese Argumentation nicht klar ist.
Prost und hth. - Alf
6

Ich werde nur eine detaillierte Referenz für die Als-ob- Regel und das flüchtige Schlüsselwort hinzufügen . (Folgen Sie am Ende dieser Seiten den Anweisungen "Siehe auch" und "Verweise", um zu den ursprünglichen Spezifikationen zurückzukehren. Cppreference.com ist jedoch viel einfacher zu lesen / zu verstehen.)

Insbesondere möchte ich, dass Sie diesen Abschnitt lesen

flüchtiges Objekt - ein Objekt, dessen Typ flüchtig qualifiziert ist, oder ein Unterobjekt eines flüchtigen Objekts oder ein veränderliches Unterobjekt eines const-flüchtigen Objekts. Jeder Zugriff (Lese- oder Schreibvorgang, Elementfunktionsaufruf usw.), der über einen glvalue-Ausdruck vom Typ flüchtig qualifiziert erfolgt, wird zum Zwecke der Optimierung als sichtbarer Nebeneffekt behandelt (dh innerhalb eines einzelnen Ausführungsthreads flüchtig) Zugriffe können nicht mit einem anderen sichtbaren Nebeneffekt optimiert oder neu angeordnet werden, der vor oder nach dem flüchtigen Zugriff sequenziert wird. Dadurch eignen sich flüchtige Objekte für die Kommunikation mit einem Signalhandler, jedoch nicht mit einem anderen Ausführungsthread, siehe std :: memory_order ). Jeder Versuch, über einen nichtflüchtigen Gl-Wert (z. B. durch eine Referenz oder einen Zeiger auf einen nichtflüchtigen Typ) auf ein flüchtiges Objekt zu verweisen, führt zu undefiniertem Verhalten.

Also das flüchtige Schlüsselwort speziell darum, die Compileroptimierung für glvalues zu deaktivieren . Das einzige, was das flüchtige Schlüsselwort hier beeinflussen kann, ist möglicherweise return x, dass der Compiler mit dem Rest der Funktion machen kann, was er will.

Inwieweit der Compiler die Rückgabe optimieren kann, hängt davon ab, inwieweit der Compiler in diesem Fall den Zugriff von x optimieren darf (da er nichts neu anordnet und streng genommen den Rückgabeausdruck nicht entfernt. Es gibt den Zugriff , aber es liest und schreibt in den Stapel, der rationalisiert werden sollte.) Wenn ich es also lese, ist dies eine Grauzone, in der der Compiler optimieren darf, und kann leicht in beide Richtungen argumentiert werden.

Randnotiz: In diesen Fällen wird immer davon ausgegangen, dass der Compiler das Gegenteil von dem tut, was Sie wollten / brauchten. Sie sollten entweder die Optimierung deaktivieren (zumindest für dieses Modul) oder versuchen, ein definierteres Verhalten für das zu finden, was Sie möchten. (Dies ist auch der Grund, warum Unit-Tests so wichtig sind.) Wenn Sie glauben, dass es sich um einen Fehler handelt, sollten Sie ihn mit den Entwicklern von C ++ besprechen.


Das alles ist immer noch sehr schwer zu lesen. Versuchen Sie also, das einzubeziehen, was ich für relevant halte, damit Sie es selbst lesen können.

glvalue Ein glvalue-Ausdruck ist entweder lvalue oder xvalue.

Eigenschaften:

Ein gl-Wert kann implizit in einen pr-Wert mit impliziter Konvertierung von lWert zu rWert, Array zu Zeiger oder Funktion zu Zeiger konvertiert werden. Ein Gl-Wert kann polymorph sein: Der dynamische Typ des Objekts, das er identifiziert, ist nicht unbedingt der statische Typ des Ausdrucks. Ein gl-Wert kann einen unvollständigen Typ haben, sofern der Ausdruck dies zulässt.


xvalue Die folgenden Ausdrücke sind xvalue-Ausdrücke:

ein Funktionsaufruf oder ein überladener Operatorausdruck, dessen Rückgabetyp eine Wertreferenz auf ein Objekt ist, wie z. B. std :: move (x); a [n], der eingebaute tiefgestellte Ausdruck, wobei ein Operand ein Array-Wert ist; am, das Mitglied des Objektausdrucks, wobei a ein r-Wert und m ein nicht statisches Datenelement vom Nichtreferenztyp ist; a. * mp, der Zeiger auf das Element des Objektausdrucks, wobei a ein r-Wert und mp ein Zeiger auf das Datenelement ist; ein ? b: c, der ternäre bedingte Ausdruck für einige b und c (Einzelheiten siehe Definition); ein Cast-Ausdruck, um den Verweis auf den Objekttyp zu bewerten, z. B. static_cast (x); Jeder Ausdruck, der nach temporärer Materialisierung ein temporäres Objekt bezeichnet. (seit C ++ 17) Eigenschaften:

Gleich wie rWert (unten). Gleich wie glvalue (unten). Insbesondere binden xvalues ​​wie alle rvalues ​​an rvalue-Referenzen, und wie alle glvalues ​​können xvalues ​​polymorph sein und xvalues, die keine Klasse sind, können cv-qualifiziert sein.


lWert Die folgenden Ausdrücke sind lWert-Ausdrücke:

Der Name einer Variablen, einer Funktion oder eines Datenelements, unabhängig vom Typ, z. B. std :: cin oder std :: endl. Selbst wenn der Variablentyp eine rWertreferenz ist, ist der Ausdruck, der aus seinem Namen besteht, ein lWertausdruck. ein Funktionsaufruf oder ein überladener Operatorausdruck, dessen Rückgabetyp eine Wertreferenz ist, wie z. B. std :: getline (std :: cin, str), std :: cout << 1, str1 = str2 oder ++ it; a = b, a + = b, a% = b und alle anderen integrierten Ausweisungs- und zusammengesetzten Zuweisungsausdrücke; ++ a und --a, die integrierten Vorinkrementierungs- und Vordekrementierungsausdrücke; * p, der eingebaute Indirektionsausdruck; a [n] und p [n], die integrierten tiefgestellten Ausdrücke, außer wenn a ein Array-Wert ist (seit C ++ 11); am, das Mitglied des Objektausdrucks, außer wenn m ein Elementaufzähler oder eine nicht statische Elementfunktion ist, oder wobei a ein r-Wert ist und m ein nicht statisches Datenelement vom Nichtreferenztyp ist; p-> m, das eingebaute Element des Zeigerausdrucks, außer wenn m ein Elementaufzähler oder eine nicht statische Elementfunktion ist; a. * mp, der Zeiger auf das Element des Objektausdrucks, wobei a ein Wert und mp ein Zeiger auf das Datenelement ist; p -> * mp, der eingebaute Zeiger auf das Element des Zeigerausdrucks, wobei mp ein Zeiger auf das Datenelement ist; a, b, der eingebaute Kommaausdruck, wobei b ein Wert ist; ein ? b: c, der ternäre bedingte Ausdruck für einige b und c (z. B. wenn beide Werte desselben Typs sind, aber Einzelheiten siehe Definition); ein String-Literal wie "Hallo Welt!"; ein Cast-Ausdruck für den Referenztyp lvalue, z. B. static_cast (x); ein Funktionsaufruf oder ein überladener Operatorausdruck, dessen Rückgabetyp ist rWertreferenz auf Funktion; Ein Cast-Ausdruck, der auf den Funktionstyp verweist, z. B. static_cast (x). (seit C ++ 11) Eigenschaften:

Gleich wie glvalue (unten). Die Adresse eines l-Werts kann verwendet werden: & ++ i 1 und & std :: endl sind gültige Ausdrücke. Ein modifizierbarer Wert kann als linker Operand der integrierten Zuweisungs- und zusammengesetzten Zuweisungsoperatoren verwendet werden. Ein l-Wert kann verwendet werden, um eine l-Wert-Referenz zu initialisieren; Dadurch wird dem durch den Ausdruck identifizierten Objekt ein neuer Name zugeordnet.


als ob Regel

Der C ++ - Compiler darf alle Änderungen am Programm vornehmen, solange Folgendes zutrifft:

1) An jedem Sequenzpunkt sind die Werte aller flüchtigen Objekte stabil (vorherige Auswertungen sind abgeschlossen, neue Auswertungen werden nicht gestartet) (bis C ++ 11). 1) Zugriffe (Lese- und Schreibvorgänge) auf flüchtige Objekte erfolgen streng gemäß der Semantik der Ausdrücke, in denen sie vorkommen. Insbesondere werden sie in Bezug auf andere flüchtige Zugriffe auf denselben Thread nicht neu angeordnet. (seit C ++ 11) 2) Beim Beenden des Programms sind die in Dateien geschriebenen Daten genau so, als ob das Programm wie geschrieben ausgeführt worden wäre. 3) Aufforderungstext, der an interaktive Geräte gesendet wird, wird angezeigt, bevor das Programm auf die Eingabe wartet. 4) Wenn das ISO C-Pragma #pragma STDC FENV_ACCESS unterstützt und auf ON gesetzt ist,


Wenn Sie die technischen Daten lesen möchten, müssen Sie diese meiner Meinung nach lesen

Verweise

C11-Norm (ISO / IEC 9899: 2011): 6.7.3 Typqualifizierer (S. 121-123)

C99-Norm (ISO / IEC 9899: 1999): 6.7.3 Typqualifizierer (S. 108-110)

Norm C89 / C90 (ISO / IEC 9899: 1990): 3.5.3 Typqualifizierer

Tezra
quelle
Es mag gemäß dem Standard nicht richtig sein, aber jeder, der sich darauf verlässt, dass der Stapel während der Ausführung von etwas anderem berührt wird, sollte die Codierung beenden. Ich würde behaupten, es ist ein Standardfehler.
Meneldal
1
@meneldal: Das ist ein viel zu weit gefasster Anspruch. Bei der Verwendung wird _AddressOfReturnAddressbeispielsweise der Stapel analysiert. Die Leute analysieren den Stapel aus triftigen Gründen, und das ist nicht unbedingt so, weil die Funktion selbst für die Richtigkeit darauf angewiesen ist.
user541686
1
glvalue ist da:return x;
geza
@geza Sorry, das ist alles schwer zu lesen. Ist das ein Wert, weil x eine Variable ist? Bedeutet dies für "Kann nicht optimiert werden", dass der Compiler überhaupt nicht optimieren kann oder dass er nicht durch Ändern des Ausdrucks optimieren kann? (Es sieht so aus, als ob der Compiler hier noch optimieren darf, da keine Zugriffsreihenfolge beibehalten werden muss und der Ausdruck immer noch aufgelöst wird, nur auf optimierte Weise.) Ich kann sehen, dass er in beide Richtungen argumentiert wird, ohne ein besseres Verständnis des Spezifikationen.
Tezra
Hier ist ein Zitat aus Ihrer eigenen Antwort :) "Die folgenden Ausdrücke sind Wertausdrücke: der Name einer Variablen ..."
Geza
-1

Ich glaube, ich habe noch nie eine lokale Variable mit volatile gesehen, die kein Zeiger auf eine flüchtige war. Wie in:

int fn() {
    volatile int *x = (volatile int *)0xDEADBEEF;
    *x = 23;   // request data, 23 = temperature 
    return *x; // return temperature
}

Die einzigen anderen Fälle von Volatilität, die ich kenne, verwenden eine globale, die in einen Signalhandler geschrieben ist. Keine Zeiger beteiligt. Oder Zugriff auf Symbole, die in einem Linker-Skript definiert sind und sich an bestimmten für die Hardware relevanten Adressen befinden.

Dort ist es viel einfacher zu begründen, warum die Optimierung die beobachtbaren Effekte verändern würde. Die gleiche Regel gilt jedoch für Ihre lokale flüchtige Variable. Der Compiler muss sich so verhalten, als ob der Zugriff auf x beobachtbar ist und ihn nicht wegoptimieren kann.

Goswin von Brederlow
quelle
3
Dies ist jedoch keine lokale flüchtige Variable, sondern ein lokaler nichtflüchtiger Zeiger auf ein flüchtiges int an einer bekannten Adresse.
Nutzlos
Das macht es einfacher, über das richtige Verhalten nachzudenken. Wie bereits erwähnt, sind die Regeln für den Zugriff auf ein flüchtiges Element für lokale Variablen und Zeiger auf flüchtige Variablen, die dereferenziert werden, dieselben.
Goswin von Brederlow
Ich spreche nur den ersten Satz Ihrer Antwort an, der darauf hindeutet, dass xIhr Code eine "lokale flüchtige Variable" ist. Ist es nicht.
Nutzlos
Ich wurde wütend, als int fn (const volatile int argument) nicht kompiliert wurde.
Joshua
4
Die Bearbeitung macht Ihre Antwort nicht falsch, aber sie beantwortet die Frage einfach nicht. Dies ist der Anwendungsfall für Lehrbücher volatileund hat nichts damit zu tun, dass es sich um einen lokalen handelt. Es könnte genauso gut static volatile int *const x = ...global sein und alles, was Sie sagen, wäre immer noch genau das gleiche. Dies ist wie zusätzliches Hintergrundwissen, das notwendig ist, um die Frage zu verstehen, die vielleicht nicht jeder hat, aber es ist keine echte Antwort.
Peter Cordes