Auswertungsreihenfolge
-
Salve,
ist es möglich, dass inwhile(ug <= og) { cout << setfill('0') << setw(8) << ug << '.' << przif(ug++) << '\n'; //++ug; }zuerst przif(ug++) ausgeführt wird, bevor ug in den Stream geschrieben wird?
Ich habe in einem Programm nämlich das Phänomen, dass bei der Ausgabe der Wert von ug um eins zu hoch ist, während der Wert von przif(ug++) das richtige Ergebnis liefert, also auch mit ug aufgerufen wird.
Wenn ich das in przif(ug) ändere und ug gesondert inkrementiere (oben auskommentiert), läuft alles wie es soll.
Ich habe versucht, ein kompilierbares Minimalbeispiel zu erstellen, allerdings kann ich das Verhalten dort nicht reproduzieren.
In meinem Programm mit obigem Code beginnt die Ausgabe mit einem ug - Wert, der um eins zu hoch ist, und endet mit einem ug - Wert, der um eins höher als og ist, während wie gesagt przif immer mit den korrekten Werten für ug aufgerufen wird, was dazu führt, dass in der Ausgabe der von przif ermittelte und ausgegebene Wert nicht zu dem ausgegebenen ug - Wert passt.Wie gesagt ist das nun kein großes praktisches Problem, da ich den Inkrement in eine eigene Zeile gepackt habe, aber trotzdem verwundert mich dieses Verhalten, vor allen Dingen, da ich es nicht ohne weiteres reproduzieren kann, und weil es auch etwas Glückssache war, dass es überhaupt aufgefallen ist.
-
Die Auswertungsreihenfolge ist in diesem Fall nicht definiert, da zwischen den beiden Ausdrücken kein Sequenzpunkt ist. Der Compiler darf Code erzeugen wie er lustig ist, theoretisch sogar parallele Abarbeitung. Manche Compiler legen die Reihenfolge auch fest, aber portabel ist es natürlich nicht, sich auf so etwas zu verlassen.
Benutze Funktionen mit Seiteneffekten daher nur in Kontexten wo eindeutig ist, was wann passiert.
-
Okay, danke für die Erläuterung. Ich hatte mich irrtümlich darauf verlassen, dass die Bestandteile, die ich an den Stream schicke, schön von links nach rechts abgearbeitet werden, schließlich landen sie ja in der richtigen Reihenfolge im Stream.
-
Ich brauch noch etwas Nachhilfe:
Ich hab mal etwas recherchiert, und hab die Aussage gefunden, dass der Aufruf einer Funktion einen Sequenzpunkt definiert.
Ist das nicht richtig, oder wieso wird in meinem Prog vor przif(ug++) nicht alles ausgewertet?
-
Belli schrieb:
Ich brauch noch etwas Nachhilfe:
Ich hab mal etwas recherchiert, und hab die Aussage gefunden, dass der Aufruf einer Funktion einen Sequenzpunkt definiert.
Ist das nicht richtig, oder wieso wird in meinem Prog vor przif(ug++) nicht alles ausgewertet?Das ist ungenau ausgedrückt oder falsch wiedergegeben.
Sequenzpunkte sind:
- Am Ende jedes vollständigen Ausdrucks. Vereinfacht gesagt ist das überall wo ein Semikolon steht (wie gesagt: Vereinfacht! Es gibt noch andere Stellen und Stellen wo Semikolons stehen, die keine Sequenzpunkte sind). Dies garantiert dir, dass Sachen die oben im Programm stehen vor Sachen passieren, die weiter unten stehen.
- Nachdem alle Argumente einer Funktion ausgewertet sind und bevor die Ausführung der Funktion beginnt. Dies garantiert dir, dass Funktionsargumente auch vollständig ausgewertet sind, bevor sie an die Funktion übergeben werden.
- Nachdem der Rückgabewert einer Funktion kopiert wurde. Dies garantiert dir, dass die Funktion vollständig ausgeführt wurde, bevor es weitergeht.
- Bei den Operatoren '&&', '||', '?' und ',', nachdem der linke Ausdruck ausgewertet wurde. Dies garantiert dir, dass die Auswertung dieser Operatoren abläuft wie man es kennt.Dies hier:
cout << setfill('0') << setw(8) << ug << '.' << przif(ug++) << '\n';Ist ein großer Ausdruck. Es ist dir folgendes garantiert:
- Am Ende wenn das Semikolon erreicht wird, wurde alles was du siehst ausgewertet. D.h. dann ist garantiert ug um 1 größer.
- Wenn die Ausführung von przif anfängt, ist ug auch um 1 größer, obwohl przif selber den alten Wert von ug übergeben bekommt.
- Sonst nichts (sofern przif keine weiteren Seiteneffekte hat)
Es ist nicht garantiert, in welcher Reihenfolge die Unterausdrücke (also insbesondereugundprzif(ug++)) dieses vollständigen Ausdrucks ausgewertet werden.edit: Das alles bezieht sich auf das beobachtbare Verhalten des Programms. Die Auswertung muss nicht wirklich physikalisch im Prozessor in dieser Reihenfolge so ablaufen. Es muss nur hinterher so aussehen, als ob es so passiert wäre. Dieser unterschied ist wichtig, falls du mal mehrthreadige Programme schreibst.
-
Ich bin mir nicht ganz sicher, aber müsste ein "flush" an der richtigen Stelle nicht abhilfe schaffen?
cout << setfill('0') << setw(8) << ug << flush << '.' << przif(ug++) << '\n';
-
Nein, warum sollte es? Flush ist eine Nachricht an den Stream und hat nichts mit dem internen Werkeln des Compilers zu tun.
-
Na die Idee war, dass flush das entleeren des Buffers an dieser Stelle erzwingt und damit ug vor der inkrementierung ausgegeben wird.
-
zachery_foxx schrieb:
Na die Idee war, dass flush das entleeren des Buffers an dieser Stelle erzwingt und damit ug vor der inkrementierung ausgegeben wird.
flush ist ein Ausdruck, wie przif und ug und ug++. Es ist nicht garantiert, dass der Aufruf des op<< vor dem flush ausgewertet wird, bevor przif oder ug++ ausgewertet wird.
-
SeppJ schrieb:
cout << setfill('0') << setw(8) << ug << '.' << przif(ug++) << '\n';Es ist nicht garantiert, in welcher Reihenfolge die Unterausdrücke (also insbesondere
ugundprzif(ug++)) dieses vollständigen Ausdrucks ausgewertet werden.Das würde mich jetzt auch mal interessieren: Sind die diversen << nicht Operatoren, also das, was intern eigentlich nur eine Funktion ist? Und ist die Funktionsaufrufsreihenfolge nicht eindeutig definiert, da ja der Rückgabewert von einer Funktion die nächste aufruft? Müsste nicht garantiert sein, dass zuerst setfill, dann setw, dann ug, dann '.', dann ug++ und dann przif (mit dem alten ug) ausgewertet wird? Denn ansonsten wäre ja nichtmal garantiert, dass er bei
const string target = "Welt"; cout << "Hallo " << target;tatsächlich "Hallo Welt" und nicht "WeltHallo " ausgibt.
-
Die einzelnen Ausdrücke können in beliebiger Reihenfolge ausgewertet werden. Das heisst aber nicht, dass sie nicht zwischengespeichert werden können, um die Reihenfolge dann zu garantieren.
-
Operator << ist nichts besonderes. Denk dir, da stünde + statt << und du willst Strings zusammensetzen:
string stringfunktion1(); string stringfunktion2(); // ... string result = "Hallo " + stringfunktion1() + " 123\n " + stringfunktion2() + " ende";Da steht auch nicht fest, in welcher Reihenfolge stringfunktion1 und stringfunktion2 abgearbeitet werden, trotzdem würdest du nie auf die Idee kommen, dass hinterher die Reihenfolge nicht stimmen würde.
Oder zum Beispiel flush: Das wäre als ob das " 123\n " eine Änderung der Auswertungsreihenfolge erzwingen könnte, bloß weil es eine besondere Bedeutung für die Ausgabe eines Strings auf den Bildschirm hat.Oder auch Grundrechenarten:
int a(); int b(); int c(); // ... int ergebnis = a() + b() * c();Bloß weil die Reihenfolge von a,b und c undefiniert ist, vergisst der Compiler trotzdem nicht, dass Punktrechnung vor Strichrechnung kommt. Und diese Regel erzwingt nicht, dass b und c zuerst ausgewertet werden!
-
Ich habs gerade bei mir getestet.
#include <iostream> #include <cmath> #include <cstdlib> int main(){ using namespace std; //Auswertungsreihenfolge cout<< (!!(cout<<"hello"))+(!!(cout<<"world"))<<'\n';//eingebauter op+ richtig rum cout<< (!!(cout<<"hello"))<<(!!(cout<<"world"))<<'\n';//selbergebauter op<< falsch rum cout<< atan2((!!(cout<<"hello")),(!!(cout<<"world")))<<'\n';//Funktionsaufruf falsch rum cout<< (!!(cout<<"hel"))<<!!(cout<<"lo ")<<(!!(cout<<"world"))<<'\n';//sogar ganz falschrum cout<< atan2(!!(cout<<"hel lo "),!!(cout<<"wor ld"))<<'\n';//Uups, Leiche, übriggeblieben beim Baslt0ln der Folgezeile cout<< atan2(atan2(!!(cout<<"hel"),!!(cout<<"lo ")),atan2(!!(cout<<"wor"),!!(cout<<"ld!")));//auch komplett falsch rum }helloworld2 worldhello11 worldhello0.785398 worldlo hel111 wor ldhel lo 0.785398 ld!worlo hel0.785398
-
Schöner Test. Der g++ bei mir macht's genau so wie bei dir. Der Intercompiler schmeißt 14 Warnungen, von wegen "operands are evaluated in unspecified order" (na sowas
) und macht daraus:helloworld2 hello1world1 helloworld0.785398 hel1lo 1world1 hel lo wor ld0.785398 hello world!0.785398
-
Ich hatte auch g++. Dann fehlt und noch ein MSVC-Lauf.
-
volkard schrieb:
Ich hatte auch g++. Dann fehlt und noch ein MSVC-Lauf.
Falls da kompliziertere Berechnungen laufen, könnte ich mir auch gut vorstellen, dass der Optimierungsgrad eine Rolle spielt.
-
volkard schrieb:
Ich hatte auch g++. Dann fehlt und noch ein MSVC-Lauf.
helloworld2 worldhello11 worldhello0.785398 worldlo hel111 wor ldhel lo 0.785398 ld!worlo hel0.785398Wie oben, musste allerdings noch casten wegen ambiguous call.
-
Bei mir (2005er) ebenfalls. Debug und Release sogar gleich.
-
zachery_foxx schrieb:
Ich bin mir nicht ganz sicher, aber müsste ein "flush" an der richtigen Stelle nicht abhilfe schaffen?
cout << setfill('0') << setw(8) << ug << flush << '.' << przif(ug++) << '\n';Bei meinen Tests mit dem fraglichen Programm hats das tatsächlich getan - aber das kann auch Zufall/Glück sein ...
-
Belli schrieb:
zachery_foxx schrieb:
Ich bin mir nicht ganz sicher, aber müsste ein "flush" an der richtigen Stelle nicht abhilfe schaffen?
cout << setfill('0') << setw(8) << ug << flush << '.' << przif(ug++) << '\n';Bei meinen Tests mit dem fraglichen Programm hats das tatsächlich getan - aber das kann auch Zufall/Glück sein ...
Ja das ist dann Glück. Die richtige Lösung ist aber denkbar simpel: das Ganze in 2 Anweisungen aufgliedern:
cout << setfill('0') << setw(8) << ug << '.'; cout << przif(ug++) << '\n';