Die meisten der hier zitierten Antworten stammen aus dem C-Standard und betonen, dass das Verhalten dieser Konstrukte nicht definiert ist. Zum Verständnis warum das Verhalten dieser Konstrukte undefiniert ist Wir wollen diese Begriffe zunächst im Zusammenhang mit der C11-Norm verstehen:
Sequenziert: (5.1.2.3)
Bei zwei beliebigen Bewertungen A
y B
wenn A
sequenziert wird, bevor B
dann die Ausführung von A
geht der Ausführung von B
.
Nicht sequenziert:
Si A
wird nicht vor oder nach B
entonces A
y B
sind nicht sequenziert.
Es gibt zwei Arten von Evaluierungen:
- Wertberechnungen die das Ergebnis eines Ausdrucks ausrechnen; und
- Nebeneffekte die Modifikationen von Objekten sind.
Sequenzpunkt:
Das Vorhandensein eines Sequenzpunktes zwischen der Auswertung von Ausdrücken A
y B
impliziert, dass jeder Wertberechnung y Nebenwirkung verbunden mit A
wird vor jeder Wertberechnung y Nebenwirkung verbunden mit B
.
Nun zu der Frage, für die Ausdrücke wie
int i = 1;
i = i++;
Norm besagt das:
6.5 Ausdrücke:
Wenn ein Seiteneffekt auf ein skalares Objekt relativ zu einem anderen Objekt nicht sequenziert ist entweder eine andere Nebenwirkung auf dasselbe skalare Objekt oder eine Wertberechnung mit dem Wert desselben skalaren Objekts, das Verhalten ist undefiniert . [...]
Daher ruft der obige Ausdruck UB auf, weil zwei Seiteneffekte auf dasselbe Objekt i
im Verhältnis zueinander nicht sequenziert ist. Das heißt, es ist nicht sequenziert, ob die Nebenwirkung durch die Zuordnung zu i
erfolgt vor oder nach der Nebenwirkung durch ++
.
Je nachdem, ob die Zuweisung vor oder nach dem Inkrement erfolgt, werden unterschiedliche Ergebnisse erzielt, und das ist der Fall von undefiniertes Verhalten .
Benennen wir die i
links von der Zuordnung sein il
und auf der rechten Seite der Zuordnung (im Ausdruck i++
) sein ir
dann sieht der Ausdruck wie folgt aus
il = ir++ // Note that suffix l and r are used for the sake of clarity.
// Both il and ir represents the same object.
Ein wichtiger Punkt zu Postfix ++
Operator ist das:
nur weil die ++
nach der Variablen kommt, bedeutet nicht, dass die Erhöhung zu spät erfolgt . Das Inkrement kann so früh erfolgen, wie es der Compiler wünscht solange der Compiler sicherstellt, dass der ursprüngliche Wert verwendet wird .
Es bedeutet, dass der Ausdruck il = ir++
könnte entweder bewertet werden als
temp = ir; // i = 1
ir = ir + 1; // i = 2 side effect by ++ before assignment
il = temp; // i = 1 result is 1
o
temp = ir; // i = 1
il = temp; // i = 1 side effect by assignment before ++
ir = ir + 1; // i = 2 result is 2
was zu zwei unterschiedlichen Ergebnissen führt 1
y 2
die von der Reihenfolge der Nebeneffekte durch Zuweisung und ++
und ruft daher UB auf.
16 Stimmen
@Jarett, nein, ich brauchte nur ein paar Hinweise auf "Sequenzpunkte". Bei der Arbeit fand ich ein Stück Code mit i = i++, ich dachte "Das ändert den Wert von i nicht". Ich habe es getestet und mich gefragt, warum. Inzwischen habe ich diese Anweisung entfernt und durch i++ ersetzt;
232 Stimmen
Ich finde es interessant, dass jeder IMMER davon ausgeht, dass solche Fragen gestellt werden, weil der Fragesteller das fragliche Konstrukt BENUTZEN will. Meine erste Vermutung war, dass PiX weiß, dass diese Konstrukte schlecht sind, aber neugierig ist, warum sie sich so verhalten, egal welchen Compiler er/sie benutzt... Und ja, was unWind sagte... es ist undefiniert, es könnte alles tun... einschließlich JCF (Jump and Catch Fire)
41 Stimmen
Ich bin neugierig: Warum warnen Compiler anscheinend nicht vor Konstrukten wie "u = u++ + ++u;", wenn das Ergebnis undefiniert ist?
0 Stimmen
Lesen Sie auch: stackoverflow.com/questions/13615243/
0 Stimmen
Warum sollten Sie erwarten, dass i = (i++) gleich 1 ist? Klammern setzen die natürliche Reihenfolge der Auswertung außer Kraft, so dass alles, was innerhalb von Klammern steht, zuerst geschieht. Also wird i++ zuerst passieren (natürlich würde i++ nach der Zuweisung passieren), wodurch es 2 wird. 2 würde dann wieder i zugewiesen werden. i ist 2.
5 Stimmen
(i++)
ergibt unabhängig von den Klammern immer noch den Wert 13 Stimmen
Was auch immer
i = (i++);
sollte, gibt es sicherlich einen klareren Weg, es zu schreiben. Das wäre selbst dann der Fall, wenn es gut definiert wäre. Selbst in Java, das das Verhalten voni = (i++);
ist es immer noch ein schlechter Code. Einfach schreibeni++;
0 Stimmen
Nur meine Meinung: solche Anweisungen sind ein undefiniertes Verhalten, weil Sie dieselbe Variable (Speicherplatz) lesen und schreiben. Dadurch kann der Compiler eine "Optimierung" durchführen, d. h. er räumt Ihren eigenen Mist auf. Natürlich bringt dies einige Einschränkungen mit sich. Das Lesen aus dem Speicher ist LANGSAM, daher werden Register verwendet und dann mit dem echten Speicher synchronisiert. Der Nebeneffekt ist, dass der Compiler nun nicht mehr weiß, welchen Wert er verwenden soll: den aus dem Speicher oder den im Register, der bereits geändert wurde. (Ich erkläre es gerne so, das macht am meisten Sinn)
3 Stimmen
@LearnOpenGLES: Das tun sie.
1 Stimmen
Ich habe einen Artikel geschrieben über Erkennung von undefiniertem Verhalten in Ausdrücken die viele ähnliche Beispiele abdeckt, aber in Bezug auf die C++11-Regeln für die Sequenzierung definiert ist. Vielleicht ist es für einige der Leser hier nützlich.
1 Stimmen
Das erinnert mich an die Fragen, die viele Softwarefirmen in Indien bei Vorstellungsgesprächen stellen. Obwohl das Verhalten undefiniert ist, versuchen sie immer noch, der Ausgabe eine Logik aufzuerlegen. Ähnliche Fragen werden auch in vielen C-Büchern von Yashwant Kanetkar gestellt. Diese Art von Fragen machen mich wirklich krank :(
1 Stimmen
@LearnOpenGLES: Mein Compiler (gcc 4.8.1) warnt mich bei Konstrukten wie u=u++ & j=i++ + ++i;
2 Stimmen
Auch wenn es in dieser Frage um
C
Es könnte für einige Aspekte von Interesse sein, dass sich dies mit der nächsten Version vonC++
mit den gewählten Garantien für die C++17-Auswertungsreihenfolge (P0145R2) Mehr: stackoverflow.com/questions/38501587/0 Stimmen
Wie in einigen Kommentaren erwähnt, gibt es in C / C++ keine expliziten Regeln für die Reihenfolge der Auswertung. Der ungewöhnlichste Fall ist APL (A Programming Language), die Ausdrücke von rechts nach links auswertet (was mehrere Zuweisungen in einer einzigen Zeile ermöglicht), wobei Klammern verwendet werden, um die Reihenfolge der Auswertung außer Kraft zu setzen.
0 Stimmen
C-Compiler liefern andere Ergebnisse als Java-Compiler:
int i=5; System.out.printf(",%d,%d,%d,%d,%d",i++,i--,++i,--i,i);
gcc 5.3.0: Output: 4,5,5,5,5,5 Java1.8 Ausgabe: 5,6,6,5,50 Stimmen
@i_am_zero: Die Tatsache, dass der Standard ein Verhalten in einer bestimmten Situation nicht vorschreibt, bedeutet nicht, dass keine Implementierungen so detailliert spezifizieren, wie sie den Code verarbeiten, dass nur ein mögliches Verhalten mit der Spezifikation vereinbar ist. Ein Problem des Standards ist, dass er nie versucht hat, alle Fälle zu katalogisieren, in denen eine Implementierung einen Umweg gehen müsste pas sich in vorhersehbarer Weise zu verhalten (z. B. durch
memcpy
in Fällen, in denen Quelle und Ziel gelegentlich gleich sind, z.B. weil die Kosten einer gelegentlichen redundanten Kopie geringer sind als...0 Stimmen
...die Kosten für die Kontrolle von jede Operation, ob die Kopie notwendig war). IMHO wäre der Standard besser, wenn er ein grundlegendes Ausführungsmodell spezifizieren würde und dann die Arten von Optimierungen, die Programmierer aktivieren können. Gegeben
x=(*p)++ + (*q)++; b=*p; c=*p;
Zum Beispiel kann es vernünftig sein, zu sagen, dass ein Compiler, wenn einige Optimierungen aktiviert sind, nach eigenem Ermessen Folgendes behandeln kannb
yc
entweder die Eins plus den Wert, der vor dem Inkrement von*p
oder einen Wert enthalten, der aus*p
zu einem beliebigen Zeitpunkt zwischen der Erhöhung und der Zuordnung zub
oc
.0 Stimmen
@i_am_zero: Solche Regeln würden den Compilern fast die gesamte nützliche Flexibilität geben, die sie unter dem gegenwärtigen Standard haben, aber wenn sie mit Möglichkeiten kombiniert werden, unbestimmte Werte in beliebige Werte zu konvertieren, könnten einige Arten von Code effizienter geschrieben werden, als es derzeit möglich ist.