Darf der Compiler ein lokales flüchtiges Element konstant falten?

25

Betrachten Sie diesen einfachen Code:

void g();

void foo()
{
    volatile bool x = false;
    if (x)
        g();
}

https://godbolt.org/z/I2kBY7

Sie können sehen, dass der potenzielle Anruf an weder optimiert gccnoch clangoptimiert wird g. Dies ist nach meinem Verständnis richtig: Die abstrakte Maschine geht davon aus, dass sich volatileVariablen jederzeit ändern können (z. B. durch Hardware-Zuordnung), sodass ein konstantes Falten der falseInitialisierung in die ifPrüfung falsch wäre.

Aber MSVC eliminiert den Aufruf von gvollständig (das Lesen und Schreiben bleibt jedoch erhalten volatile!). Ist das standardkonformes Verhalten?


Hintergrund: Ich verwende gelegentlich diese Art von Konstrukt, um die Debugging-Ausgabe im laufenden Betrieb ein- und ausschalten zu können: Der Compiler muss den Wert immer aus dem Speicher lesen. Wenn Sie also diese Variable / diesen Speicher während des Debuggens ändern, sollte der Steuerungsfluss entsprechend geändert werden . Der MSVC-Ausgang liest den Wert erneut, ignoriert ihn jedoch (vermutlich aufgrund ständiger Faltung und / oder Eliminierung toten Codes), was meine Absichten hier natürlich zunichte macht.


Bearbeitungen:

  • Die Eliminierung der Lese- und Schreibvorgänge volatilewird hier erläutert: Darf ein Compiler eine lokale flüchtige Variable optimieren? (Danke Nathan!). Ich denke, der Standard ist völlig klar, dass diese Lese- und Schreibvorgänge stattfinden müssen . Diese Diskussion behandelt jedoch nicht, ob es für den Compiler legal ist, die Ergebnisse dieser Lesevorgänge als selbstverständlich zu betrachten und auf dieser Grundlage zu optimieren. Ich nehme an, dass dies im Standard unter- / nicht spezifiziert ist , aber ich würde mich freuen, wenn mir jemand das Gegenteil beweisen würde.

  • Ich kann natürlich xeine nicht lokale Variable erstellen, um das Problem zu umgehen. Diese Frage ist eher aus Neugier.

Max Langhof
quelle
3
Dies scheint mir ein offensichtlicher Compiler-Fehler zu sein.
Sam Varshavchik
1
Soweit ich weiß, ist dies nach der Als-ob-Regel legal. Der Compiler kann beweisen, dass das Objekt zwar flüchtig ist, sein Status jedoch nicht geändert werden kann, sodass es ausgeklappt werden kann. Ich bin nicht sicher genug, um das in eine Antwort zu setzen, aber ich denke, es ist richtig.
NathanOliver
3
Ich halte das Argument des OP, dass die Variable von einem Debugger geändert werden kann, jedoch für vernünftig. Vielleicht sollte jemand einen Fehlerbericht bei MSVC einreichen.
Brian
2
@curiousguy Auch wenn Sie das Ergebnis verwerfen und / oder den genauen Wert annehmen, haben Sie es dennoch gelesen.
Deduplikator
2
Interessanterweise funktioniert das nur für x64. Die x86-Version ruft immer noch g () godbolt.org/z/nc3Y-f
Jerry Jeremiah

Antworten:

2

Ich denke, [intro.execution] (Absatznummer variiert) könnte verwendet werden, um das MSVC-Verhalten zu erklären:

Eine Instanz jedes Objekts mit automatischer Speicherdauer ist jedem Eintrag in seinem Block zugeordnet. Ein solches Objekt existiert und behält seinen zuletzt gespeicherten Wert während der Ausführung des Blocks und während der Block angehalten wird ...

Der Standard erlaubt nicht die Eliminierung eines Durchlesens eines flüchtigen Gl-Werts, aber der obige Absatz könnte so interpretiert werden, dass er die Vorhersage des Wertes ermöglicht false.


Übrigens sagt das der C-Standard (N1570 6.2.4 / 2)

Ein Objekt existiert, hat eine konstante Adresse und behält seinen zuletzt gespeicherten Wert während seiner gesamten Lebensdauer bei. 34


34) Bei einem flüchtigen Objekt muss der letzte Speicher nicht explizit im Programm sein.

Es ist unklar, ob in einem Objekt mit automatischer Speicherdauer im C-Speicher / Objektmodell ein nicht expliziter Speicher vorhanden sein könnte.

Sprachanwalt
quelle
Stimmen Sie zu, dass der Compiler möglicherweise weiß, wann nicht explizite Speicher auf der Zielplattform möglich sind
MM
1
Wenn dies zutrifft, sind lokale flüchtige Objekte (zumindest bei MSVC) völlig sinnlos? Gibt es etwas, das volatileSie durch Hinzufügen kaufen (außer überflüssigem Lesen / Schreiben), wenn es zu Optimierungszwecken ignoriert wird?
Max Langhof
1
@MaxLanghof Es gibt einen Unterschied zwischen völlig sinnlos und nicht ganz dem Effekt, den Sie wollen / erwarten.
Deduplikator
1
@MaxLanghof Zwischenergebnisse von Gleitkommaberechnungen werden manchmal in ein 80-Bit-Register hochgestuft, was zu Genauigkeitsproblemen führt. Ich glaube, dies ist ein gcc-ismus und wird vermieden, indem alle diese Doppel als volatil deklariert werden. Siehe: gcc.gnu.org/bugzilla/show_bug.cgi?id=323
ForeverLearning
1
@philipxy PS Siehe meine Antwort Ich weiß, dass der Zugriff implementierungsdefiniert ist. Bei der Frage geht es nicht um den Zugriff (auf das Objekt wird zugegriffen), sondern um die Vorhersage des Werts.
Sprachanwalt
2

TL; DR Der Compiler kann bei jedem flüchtigen Zugriff tun, was er will. Die Dokumentation muss Ihnen jedoch Folgendes mitteilen: "Die Semantik eines Zugriffs über einen flüchtigen Wert ist implementierungsdefiniert."


Der Standard definiert für ein Programm zulässige Sequenzen von "flüchtigen Zugriffen" und anderem "beobachtbarem Verhalten" (erreicht durch "Nebenwirkungen"), die eine Implementierung gemäß der "Als-ob" -Regel "einhalten muss.

Aber der Standard sagt (meine kühne Betonung):

Arbeitsentwurf, Standard für die Programmiersprache C ++
Dokumentnummer: N4659
Datum: 2017-03-21

§ 10.1.7.1 Die Lebenslauf-Qualifikanten

5 Die Semantik eines Zugriffs über einen flüchtigen Wert ist implementierungsdefiniert. […]

Ähnliches gilt für interaktive Geräte (meine Fettdruck-Betonung):

§ 4.6 Programmausführung

5 Eine konforme Implementierung, die ein wohlgeformtes Programm ausführt, muss dasselbe beobachtbare Verhalten erzeugen wie eine der möglichen Ausführungen der entsprechenden Instanz der abstrakten Maschine mit demselben Programm und derselben Eingabe. [...]

7 Die geringsten Anforderungen an eine konforme Implementierung sind:

(7.1) - Zugriffe durch flüchtige Werte werden streng nach den Regeln der abstrakten Maschine bewertet.
(7.2) - Bei Beendigung des Programms müssen alle in Dateien geschriebenen Daten mit einem der möglichen Ergebnisse identisch sein, die die Ausführung des Programms gemäß der abstrakten Semantik ergeben hätte.
(7.3) - Die Eingabe- und Ausgabedynamik interaktiver Geräte muss so erfolgen, dass die Aufforderung tatsächlich ausgegeben wird, bevor ein Programm auf die Eingabe wartet.Was ein interaktives Gerät ausmacht, ist implementierungsdefiniert.

Diese werden zusammen als beobachtbares Verhalten des Programms bezeichnet. [...]

(Wie auch immer welcher spezifische Code für ein Programm generiert wird , ist vom Standard nicht festgelegt.)

Obwohl der Standard besagt, dass flüchtige Zugriffe nicht aus den abstrakten Sequenzen abstrakter Maschineneffekte und daraus resultierenden beobachtbaren Verhaltensweisen herausgelöst werden können, die ein Code (möglicherweise) definiert, können Sie nicht erwarten, dass sich etwas im Objektcode oder in der realen Welt widerspiegelt Verhalten, es sei denn, Ihre Compiler-Dokumentation sagt es Ihnen was einen flüchtigen Zugriff ausmacht . Das Gleiche gilt für interaktive Geräte.

Wenn Sie in volatil vis a vis interessiert sind die abstrakten Sequenzen von abstrakten Maschine Nebenwirkungen und / oder daraus folgenden beobachtbaren Verhalten , dass einige Code (vielleicht) definiert dann so sagen . Wenn Sie jedoch daran interessiert sind, welcher entsprechende Objektcode generiert wird, müssen Sie dies im Kontext Ihres Compilers und Ihrer Kompilierung interpretieren .

Chronisch glauben Menschen fälschlicherweise, dass bei flüchtigen Zugriffen eine abstrakte Maschinenbewertung / -lesung ein implementiertes Lesen und eine abstrakte Maschinenzuweisung / -schreibung ein implementiertes Schreiben verursacht. Es gibt keine Grundlage für diese Annahme, wenn keine Implementierungsdokumentation dies sagt. Wenn / wenn die Implementierung sagt, dass sie bei einem "flüchtigen Zugriff" tatsächlich etwas tut , können die Leute zu Recht etwas erwarten - vielleicht die Erzeugung eines bestimmten Objektcodes.

philipxy
quelle
1
Ich meine, das läuft darauf hinaus, "wenn ich einen Computer finde, auf dem alle genannten Nebenwirkungen No-Ops sind, habe ich eine legale C ++ - Implementierung, indem ich jedes Programm zu einem No-Op kompiliere" . Natürlich sind wir an praktisch beobachtbaren Effekten interessiert, da abstrakte maschinelle Nebenwirkungen tautologisch abstrakt sind. Dies erfordert ein grundlegendes Konzept von Nebenwirkungen, und ich würde argumentieren, dass "flüchtige Zugriffe zu expliziten Anweisungen für den Speicherzugriff führen" ein Teil davon ist (auch wenn es dem Standard egal ist), also kaufe ich das "Sagen" nicht wirklich wenn Sie Code anstelle von abstrakter Semantik wollen ". Trotzdem +1.
Max Langhof
Ja, die Qualität der Implementierung ist relevant. Abgesehen von Schreibvorgängen in Dateien sind "Observables" jedoch implementierungsdefiniert. Wenn Sie in der Lage sein möchten, Haltepunkte zu setzen, auf bestimmten tatsächlichen Speicher zuzugreifen, "flüchtig" zu ignorieren usw., wenn abstrakte flüchtige Lese- und Schreibvorgänge ausgeführt werden , müssen Sie Ihren Compiler-Writer dazu bringen, den entsprechenden Code auszugeben . PS In C ++ wird "Nebeneffekt" nicht für das Beobachtungsverhalten an sich verwendet, sondern zur Beschreibung der Teilordnungsbewertung von Unterausdrücken.
Philipxy
Möchtest du das erklären? Welche Quelle zitieren Sie und was ist der Fehler?
Max Langhof
Ich meine nur, dass eine abstrakte beobachtbare Maschine nur dann in der realen Welt beobachtbar ist, wenn und wie die Implementierung dies sagt.
philipxy
1
Wollen Sie damit sagen, dass eine Implementierung behaupten könnte, dass es kein interaktives Gerät gibt, sodass jedes Programm alles kann und es dennoch korrekt wäre? (Hinweis: Ich verstehe Ihre Betonung auf interaktive Geräte nicht.)
neugieriger Kerl
-1

Ich glaube, es ist legal, den Scheck zu überspringen.

Der Absatz, den jeder gerne zitiert

34) Bei einem flüchtigen Objekt muss der letzte Speicher nicht explizit im Programm sein

bedeutet nicht, dass eine Implementierung davon ausgehen muss, dass solche Speicher jederzeit oder für jede flüchtige Variable möglich sind. Eine Implementierung weiß, welche Speicher möglich sind. Beispielsweise ist es durchaus vernünftig anzunehmen, dass solche impliziten Schreibvorgänge nur für flüchtige Variablen auftreten, die Geräteregistern zugeordnet sind, und dass eine solche Zuordnung nur für Variablen mit externer Verknüpfung möglich ist. Oder eine Implementierung kann annehmen, dass solche Schreibvorgänge nur an wortgroßen, wortausgerichteten Speicherstellen stattfinden.

Trotzdem denke ich, dass MSVC-Verhalten ein Fehler ist. Es gibt keinen realen Grund, den Anruf zu optimieren. Eine solche Optimierung mag konform sein, ist aber unnötig böse.

n. 'Pronomen' m.
quelle
Kannst du erklären, warum es böse ist? In den Code-Shows kann die Funktion buchstäblich nie aufgerufen werden.
David Schwartz
@DavidSchwartz Sie können dies erst schließen, nachdem Sie die Semantik lokaler flüchtiger Variablen angegeben haben (siehe den oben zitierten Absatz). Der Standard selbst stellt fest, dass volatiledies ein Hinweis auf die Implementierung sein soll, dass sich der Wert durch Mittel ändern kann, die der Implementierung unbekannt sind.
Max Langhof
@MaxLanghof Es gibt keine Möglichkeit, dass die Implementierung mit etwas Unbekanntem richtig umgehen kann. Was nützliche Plattformen tatsächlich tun, ist anzugeben, wofür Sie volatileauf dieser Plattform verwenden können und was nicht. Außerhalb dieser Spezifikation wird es immer ein Mist-Shooting sein.
David Schwartz
@DavidSchwartz Natürlich kann es - indem man der Semantik (insbesondere den Lese- und Schreibvorgängen) der abstrakten Maschine folgt. Es ist möglicherweise nicht in der Lage, richtig zu optimieren - das ist der Punkt des Standards. Nun, es ist eine Notiz und daher nicht normativ, und wie wir beide sagten, kann die Implementierung spezifizieren, was volatiletut, und sich daran halten. Mein Punkt ist, dass der Code selbst (gemäß der C ++ - Standard- / abstrakten Maschine) es Ihnen nicht erlaubt zu bestimmen, ob gaufgerufen werden kann.
Max Langhof
@DavidSchwartz Der Code zeigt so etwas nicht an, da das Fehlen impliziter Schreibvorgänge nicht aus dem Code folgt.
n. 'Pronomen' m.