Kürzlich gab es in einem Interview eine folgende objektive Frage.
int a = 0;
cout << a++ << a;
Antworten:
ein. 10
b. 01
c. undefiniertes Verhalten
Ich antwortete auf Wahl b, dh die Ausgabe wäre "01".
Zu meiner Überraschung wurde mir später von einem Interviewer gesagt, dass die richtige Antwort Option c: undefiniert ist.
Jetzt kenne ich das Konzept der Sequenzpunkte in C ++. Das Verhalten ist für die folgende Anweisung undefiniert:
int i = 0;
i += i++ + i++;
aber nach meinem für die Aussage zu verstehen cout << a++ << a
, das ostream.operator<<()
würde zweimal aufgerufen werden, zuerst mit ostream.operator<<(a++)
und später ostream.operator<<(a)
.
Ich habe auch das Ergebnis auf dem VS2010-Compiler überprüft und seine Ausgabe ist ebenfalls '01'.
10
, es wäre entweder01
oder00
. (c++
Wertet immer auf den Wertc
hatte , bevor erhöht wird). Und selbst wenn es nicht undefiniert wäre, wäre es immer noch schrecklich verwirrend.Antworten:
Sie können sich vorstellen:
Wie:
C ++ garantiert, dass alle Nebenwirkungen früherer Bewertungen an Sequenzpunkten durchgeführt wurden . Es gibt keine Sequenzpunkte zwischen der Auswertung von Funktionsargumenten, was bedeutet, dass das Argument
a
vorstd::operator<<(std::cout, a++)
oder nach dem Argument ausgewertet werden kann . Das Ergebnis des oben Gesagten ist also undefiniert.C ++ 17 Update
In C ++ 17 wurden die Regeln aktualisiert. Bestimmtes:
Dies bedeutet, dass der Code ein Ergebnis erzeugen muss
b
, das ausgegeben wird01
.Weitere Informationen finden Sie unter P0145R3 Verfeinern der Ausdrucksauswertungsreihenfolge für idiomatisches C ++ .
quelle
c
Typ hatint
, sind dieoperator<<
hier Mitgliedsfunktionen.operator<<
es sich um eine Mitgliedsfunktion oder eine freistehende Funktion handelt, wirkt sich nicht auf Sequenzpunkte aus.So the result of the above is undefined.
Ihre Erklärung ist nur für nicht spezifizierte , nicht für undefinierte gut . JamesKanze erklärte, wie verdammt undefiniert es in seiner Antwort war .Technisch gesehen ist dies insgesamt undefiniertes Verhalten .
Die Antwort hat jedoch zwei wichtige Aspekte.
Die Code-Anweisung:
wird bewertet als:
Der Standard definiert nicht die Reihenfolge der Bewertung von Argumenten für eine Funktion.
Also entweder:
std::operator<<(std::cout, a++)
wird zuerst ausgewertet odera
wird zuerst ausgewertet oderDiese Bestellung ist gemäß Standard nicht spezifiziert [Ref. 1] .
[Ref 1] C ++ 03 5.2.2 Funktionsaufruf
Abs. 8
Ferner gibt es keinen Sequenzpunkt zwischen der Auswertung von Argumenten für eine Funktion, aber ein Sequenzpunkt existiert erst nach Auswertung aller Argumente [Ref. 2] .
[Ref 2] C ++ 03 1.9 Programmausführung [intro.execution]:
Abs. 17:
Beachten Sie, dass hier
c
mehrmals auf den Wert von zugegriffen wird, ohne dass ein dazwischenliegender Sequenzpunkt vorhanden ist. Diesbezüglich lautet der Standard:[Ref 3] C ++ 03 5 Ausdrücke [Ausdruck]:
Abs. 4:
Der Code wird
c
mehrmals geändert, ohne dass ein Sequenzpunkt dazwischen liegt, und es wird nicht darauf zugegriffen, um den Wert des gespeicherten Objekts zu bestimmen. Dies ist ein klarer Verstoß gegen die obige Klausel, und daher ist das vom Standard vorgeschriebene Ergebnis undefiniertes Verhalten [Ref. 3] .quelle
Sequenzpunkte nur definieren Teilbestellung. In Ihrem Fall haben Sie (sobald die Überlastungsauflösung abgeschlossen ist):
Es gibt einen Sequenzpunkt zwischen dem
a++
und dem ersten Aufruf vonstd::ostream::operator<<
und einen Sequenzpunkt zwischen dem zweitena
und dem zweiten Aufruf vonstd::ostream::operator<<
, aber es gibt keinen Sequenzpunkt zwischena++
unda
; Die einzigen Ordnungsbeschränkungen bestehen darin, dassa++
sie vor dem ersten Aufruf von vollständig ausgewertet werden (einschließlich Nebenwirkungen)operator<<
und dass die zweitea
vor dem zweiten Aufruf von vollständig ausgewertet wirdoperator<<
. (Es gibt auch kausale Ordnungsbeschränkungen: Der zweite Aufruf vonoperator<<
kann dem ersten nicht vorangehen, da er die Ergebnisse des ersten als Argument erfordert.) §5 / 4 (C ++ 03) besagt:Einer der zulässigen Ordnungen des Ausdrucks ist
a++
,a
zunächst Anrufoperator<<
, auf den zweiten Anrufoperator<<
; Dies ändert den gespeicherten Wert vona
(a++
) und greift darauf zu, außer um den neuen Wert (den zweitena
) zu bestimmen. Das Verhalten ist undefiniert.quelle
c
waren ein Benutzertyp mit einem Benutzer definiert++
, stattint
, wären die Ergebnisse nicht spezifizieren, aber es gäbe kein undefiniertes Verhalten sein.c
infoo(foo(bar(c)), c)
? Es gibt einen Sequenzpunkt, an dem Funktionen aufgerufen werden und wenn sie zurückkehren, aber zwischen den Auswertungen der beiden ist kein Funktionsaufruf erforderlichc
.c
es sich um eine UDT handeln würde , wären die überladenen Operatoren Funktionsaufrufe und würden einen Sequenzpunkt einführen, sodass das Verhalten nicht undefiniert wäre. Es ist jedoch immer noch nicht angegeben, ob der Unterausdruckc
vorher oder nachher ausgewertetc++
wurde. Ob Sie also die inkrementierte Version erhalten haben oder nicht, wird nicht angegeben (und muss theoretisch nicht jedes Mal gleich sein).c
undc++
, so dass die beiden in beliebiger Reihenfolge auftreten können. Semikolons ... Sie verursachen nur dann einen Sequenzpunkt, wenn sie vollständige Ausdrücke sind. Weitere wichtige Sequenzpunkte sind der Funktionsaufruf:f(c++)
wird der erhöhte sehenc
inf
, und der Komma - Operator,&&
,||
und?:
auch Ursache Sequenzpunkte.Die richtige Antwort ist, die Frage zu stellen. Die Aussage ist inakzeptabel, da ein Leser keine klare Antwort sehen kann. Eine andere Sichtweise ist, dass wir Nebenwirkungen (c ++) eingeführt haben, die die Interpretation der Aussage erheblich erschweren. Prägnanter Code ist großartig, vorausgesetzt, seine Bedeutung ist klar.
quelle