Kürzlich in einem Interview gab es eine folgende objektive Artfrage.
int a = 0;
cout << a++ << a;
Antworten:
A. 10
B. 01
C. undefiniertes Verhalten
Ich habe Wahl b beantwortet, dh Ausgabe wäre “01”.
Aber zu meiner Überraschung wurde mir später von einem Interviewer gesagt, dass die richtige Antwort Option c ist: undefiniert.
Nun, ich kenne das Konzept der Sequenzpunkte in C++. Das Verhalten ist für die folgende Anweisung nicht definiert:
int i = 0;
i += i++ + i++;
aber nach meinem Verständnis für die Aussage cout << a++ << a der ostream.operator<<() würde zweimal angerufen 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’.
Hast du nach einer Erklärung gefragt? Ich interviewe oft potenzielle Kandidaten und bin sehr daran interessiert, Fragen zu erhalten, das zeigt Interesse.
– Brady
28. Mai 2012 um 10:14 Uhr
@jrok Es ist undefiniertes Verhalten. Alles, was die Implementierung tut (einschließlich des Versendens einer beleidigenden E-Mail in Ihrem Namen an Ihren Chef), ist konform.
– James Kanze
28. Mai 2012 um 11:12 Uhr
Diese Frage schreit nach einem C++11 (the Strom Version von C++) Antwort, die keine Sequenzpunkte erwähnt. Leider kenne ich mich mit dem Ersatz von Sequenzpunkten in C++11 nicht aus.
– CB Bailey
28. Mai 2012 um 11:34 Uhr
Wenn es nicht undefiniert wäre, könnte es definitiv nicht sein 10es wäre entweder 01 oder 00. (c++ wird immer den Wert auswerten c hatte Vor inkrementiert wird). Und selbst wenn es nicht undefiniert wäre, wäre es immer noch schrecklich verwirrend.
– linksherum
28. Mai 2012 um 11:38 Uhr
Weißt du, als ich den Titel „cout << c++ << c“ las, dachte ich kurz daran als eine Aussage über die Beziehung zwischen den Sprachen C und C++ und einer anderen namens „cout“. Weißt du, wie jemand gesagt hat, dass er dachte, dass „cout“ C++ viel unterlegen sei und dass C++ C viel unterlegen sei – und wahrscheinlich aufgrund der Transitivität, dass „cout“ sehr, sehr viel schlechter als C. 🙂
C++ garantiert, dass alle Seiteneffekte früherer Auswertungen durchgeführt wurden Sequenzpunkte. Es gibt keine Sequenzpunkte zwischen der Auswertung der Funktionsargumente, was dieses Argument bedeutet a kann vor dem Argument ausgewertet werden std::operator<<(std::cout, a++) oder danach. Das Ergebnis des Obigen ist also undefiniert.
C++17-Update
In C++17 wurden die Regeln aktualisiert. Bestimmtes:
In einem Verschiebungsoperatorausdruck E1<<E2 und E1>>E2jede Wertberechnung und Nebenwirkung von E1 wird vor jeder Wertberechnung und Nebenwirkung von sequenziert E2.
Das bedeutet, dass der Code erforderlich ist, um ein Ergebnis zu erzeugen bdie ausgibt 01.
@Maxim: Danke für die Erklärung. Bei den von Ihnen erläuterten Aufrufen wäre es ein undefiniertes Verhalten. Aber jetzt habe ich noch eine Frage (möglicherweise noch eine, und ich vermisse etwas Grundlegendes und denke laut nach): Wie haben Sie abgeleitet, dass die globale Version von std::operator<<() anstelle von ostream::operator< aufgerufen wird <() Mitgliedsversion. Beim Debuggen lande ich eher in einer Member-Version des ostream::operator<<()-Aufrufs als in der globalen Version, und das ist der Grund, warum ich anfangs dachte, dass die Antwort 01 wäre
– Pravs
28. Mai 2012 um 11:26 Uhr
@Maxim Nicht das es einen anders macht, aber da c Typ hat intder operator<< Hier sind Member-Funktionen.
– James Kanze
28. Mai 2012 um 11:32 Uhr
@pravs: ob operator<< eine Mitgliedsfunktion oder eine freistehende Funktion ist, wirkt sich nicht auf Sequenzpunkte aus.
– Maxim Egorushkin
28. Mai 2012 um 11:39 Uhr
Der ‘sequence point’ wird im C++-Standard nicht mehr verwendet. Sie war unpräzise und wurde durch die Relation „sequenziert vor/sequenziert nach“ ersetzt.
– Rafal Dowgird
28. Mai 2012 um 14:01 Uhr
So the result of the above is undefined. Ihre Erklärung ist nur gut für nicht spezifiziertnicht für nicht definiert. JamesKanze erklärte, wie es umso vernichtender wird nicht definiert aber in seiner Antwort.
– Deduplizierer
31. Mai 2014 um 0:45 Uhr
Alok Speichern
Technisch gesehen ist dies insgesamt Undefiniertes Verhalten.
Aber es gibt zwei wichtige Aspekte bei der Antwort.
Der Standard definiert nicht die Reihenfolge der Auswertung von Argumenten für eine Funktion.
Also entweder:
std::operator<<(std::cout, a++) wird zuerst ausgewertet bzw
awird zuerst ausgewertet bzw
es kann sich um eine beliebige implementierungsdefinierte Reihenfolge handeln.
Diese Reihenfolge ist Nicht spezifiziert[Ref 1] nach Norm.
[Ref 1]C++03 5.2.2 Funktionsaufruf Abs. 8
Die Reihenfolge der Bewertung der Argumente ist nicht festgelegt. Alle Seiteneffekte der Auswertung von Argumentausdrücken werden wirksam, bevor die Funktion eingegeben wird. Die Reihenfolge der Auswertung des Postfix-Ausdrucks und der Argumentausdrucksliste ist nicht festgelegt.
Ferner gibt es keinen Sequenzpunkt zwischen der Auswertung von Argumenten für eine Funktion, sondern ein Sequenzpunkt existiert nur nach der Auswertung aller Argumente[Ref 2].
Beim Aufruf einer Funktion (unabhängig davon, ob es sich um eine Inline-Funktion handelt oder nicht) gibt es nach der Auswertung aller Funktionsargumente (falls vorhanden) einen Sequenzpunkt, der vor der Ausführung von Ausdrücken oder Anweisungen im Funktionsrumpf stattfindet.
Beachten Sie, dass hier der Wert von c mehr als einmal ohne dazwischenliegenden Sequenzpunkt zugegriffen wird, heißt es dazu in der Norm:
[Ref 3]C++03 5 Ausdrücke [expr]: Absatz 4:
…. Zwischen dem vorherigen und dem nächsten Sequenzpunkt darf ein Skalarobjekt seinen gespeicherten Wert höchstens einmal durch die Auswertung eines Ausdrucks modifizieren. Außerdem soll auf den vorherigen Wert nur zugegriffen werden, um den zu speichernden Wert zu bestimmen. Die Anforderungen dieses Absatzes müssen für jede zulässige Reihenfolge der Teilausdrücke eines vollständigen Ausdrucks erfüllt werden; andernfalls ist das Verhalten undefiniert.
Der Code ändert sich c mehr als einmal ohne dazwischenliegenden Sequenzpunkt 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 das von der Norm vorgeschriebene Ergebnis Undefiniertes Verhalten[Ref 3].
Ich meine, c wird nur einmal geändert, sodass das Programm legal 01 oder 10 drucken kann, aber nichts Seltsames tun kann. Ist mein Verständnis richtig?
– jrok
28. Mai 2012 um 10:40 Uhr
Technisch gesehen ist das Verhalten undefiniert, da ein Objekt modifiziert wird und an anderer Stelle ohne einen dazwischenliegenden Sequenzpunkt darauf zugegriffen wird. Undefiniert ist nicht nicht spezifiziert; sie lässt der Umsetzung noch mehr Spielraum.
– James Kanze
28. Mai 2012 um 11:14 Uhr
@Als Ja. Ich hatte Ihre Bearbeitungen nicht gesehen (obwohl ich auf jroks Aussage reagiert hatte, dass das Programm etwas Seltsames nicht tun kann – es kann). Ihre bearbeitete Version ist soweit gut, aber meiner Meinung nach ist das Schlüsselwort Teilbestellung; Sequenzpunkte führen nur eine teilweise Ordnung ein.
– James Kanze
28. Mai 2012 um 11:30 Uhr
Der neue C++0x-Standard sagt im Wesentlichen das Gleiche, aber in anderen Abschnitten und in anderen Formulierungen 🙂 Zitat: (1.9 Program Execution [intro.execution]Abs. 15): „Wenn eine Nebenwirkung auf ein skalares Objekt relativ zu einer anderen Nebenwirkung auf dasselbe skalare Objekt oder einer Wertberechnung unter Verwendung des Werts desselben skalaren Objekts nicht sequenziert ist, ist das Verhalten undefiniert.“
– Rafal Dowgird
28. Mai 2012 um 14:41 Uhr
Ich glaube, diese Antwort enthält einen Fehler. “std::cout<ist
angegebenen.
– Christoph Schmidt
21. September 2013 um 4:08 Uhr
James Kanze
Sequenzpunkte definieren nur a teilweise Bestellung. In Ihrem Fall haben Sie (sobald die Überladungsauflösung abgeschlossen ist):
std::cout.operator<<( a++ ).operator<<( a );
Es gibt einen Sequenzpunkt zwischen den a++ und der erste Anruf zu std::ostream::operator<<und es gibt einen Sequenzpunkt zwischen dem zweiten a und der zweite Aufruf an std::ostream::operator<<aber es gibt keinen Sequenzpunkt dazwischen a++ und a; die einzigen Bestellbeschränkungen sind die a++ vor dem ersten Anruf vollständig ausgewertet werden (einschließlich Nebenwirkungen). operator<<und das die zweite a vor dem zweiten Aufruf vollständig ausgewertet werden operator<<. (Es gibt auch kausale Ordnungseinschränkungen: der zweite Aufruf von to operator<< kann dem ersten nicht vorangestellt werden, da es die Ergebnisse des ersten als Argument benötigt.) §5/4 (C++03) besagt:
Sofern nicht anders angegeben, ist die Reihenfolge der Auswertung von Operanden einzelner Operatoren und Unterausdrücke einzelner Ausdrücke sowie die Reihenfolge, in der Seiteneffekte auftreten, nicht festgelegt. Zwischen dem vorherigen und dem nächsten Sequenzpunkt darf ein Skalarobjekt seinen gespeicherten Wert höchstens einmal durch die Auswertung eines Ausdrucks modifizieren. Außerdem soll auf den vorherigen Wert nur zugegriffen werden, um den zu speichernden Wert zu bestimmen. Die Anforderungen dieses Absatzes müssen für jede zulässige Reihenfolge der Teilausdrücke eines vollständigen Ausdrucks erfüllt werden; andernfalls ist das Verhalten undefiniert.
Eine der zulässigen Reihenfolgen Ihres Ausdrucks ist a++, aerster Aufruf an operator<<zweiter Aufruf an operator<<; dies modifiziert den gespeicherten Wert von a (a++) und greift darauf zu, außer um den neuen Wert zu bestimmen (die zweite a), ist das Verhalten undefiniert.
Ein Fang von Ihrem Zitat des Standards. Das IIRC „außer wo angegeben“ enthält eine Ausnahme beim Umgang mit einem überladenen Operator, die den Operator als Funktion behandelt und daher einen Sequenzpunkt zwischen dem ersten und zweiten Aufruf von std::ostream::operator<<(int ). Bitte korrigieren Sie mich, wenn ich falsch liege.
– Christoph Schmidt
21. September 2013 um 3:14 Uhr
@ChristopherSmith Ein überladener Operator verhält sich wie ein Funktionsaufruf. Wenn c waren ein Benutzertyp mit einem Benutzerdefiniert ++anstatt intwären die Ergebnisse nicht spezifiziert, aber es gäbe kein undefiniertes Verhalten.
– James Kanze
21. September 2013 um 10:28 Uhr
@ChristopherSmith Wo siehst du einen Sequenzpunkt zwischen den beiden c in foo(foo(bar(c)), c)? Es gibt einen Sequenzpunkt, wenn Funktionen aufgerufen werden und wenn sie zurückkehren, aber zwischen den Auswertungen der beiden ist kein Funktionsaufruf erforderlich c.
– James Kanze
2. Mai 2014 um 8:21 Uhr
@ChristopherSmith Wenn c war ein UDT, der überladene Operatoren möchten Funktionsaufrufe sein und einen Sequenzpunkt einführen würden, damit das Verhalten nicht undefiniert wäre. Aber es wäre noch nicht spezifiziert, ob der Unterausdruck c vorher oder nachher ausgewertet wurde c++ob Sie also die inkrementierte Version erhalten haben oder nicht, würde nicht angegeben (und müsste theoretisch nicht jedes Mal gleich sein).
– James Kanze
30. Dezember 2014 um 20:18 Uhr
@ChristopherSmith Alles vor dem Sequenzpunkt wird vor allem nach dem Sequenzpunkt passieren. Aber Sequenzpunkte definieren nur eine teilweise Ordnung. In dem betreffenden Ausdruck gibt es beispielsweise keinen Sequenzpunkt zwischen den Teilausdrücken c und c++, also können die beiden in beliebiger Reihenfolge auftreten. Was Semikolons angeht… Sie bewirken nur insofern einen Sequenzpunkt, als es sich um Vollausdrücke handelt. Weitere wichtige Ablaufpunkte sind der Funktionsaufruf: f(c++) wird die Erhöhung sehen c in fund der Kommaoperator, &&, || und ?: verursachen auch Sequenzpunkte.
– James Kanze
4. Januar 2015 um 17:40 Uhr
Die richtige Antwort ist, die Frage zu hinterfragen. Die Aussage ist nicht akzeptabel, weil ein Leser keine klare Antwort sehen kann. Eine andere Betrachtungsweise ist, dass wir Nebeneffekte (c++) eingeführt haben, die die Interpretation der Anweisung erheblich erschweren. Kurzer Code ist großartig, vorausgesetzt, seine Bedeutung ist klar.
9877100cookie-checkWas ist die richtige Antwort für coutyes
Hast du nach einer Erklärung gefragt? Ich interviewe oft potenzielle Kandidaten und bin sehr daran interessiert, Fragen zu erhalten, das zeigt Interesse.
– Brady
28. Mai 2012 um 10:14 Uhr
@jrok Es ist undefiniertes Verhalten. Alles, was die Implementierung tut (einschließlich des Versendens einer beleidigenden E-Mail in Ihrem Namen an Ihren Chef), ist konform.
– James Kanze
28. Mai 2012 um 11:12 Uhr
Diese Frage schreit nach einem C++11 (the Strom Version von C++) Antwort, die keine Sequenzpunkte erwähnt. Leider kenne ich mich mit dem Ersatz von Sequenzpunkten in C++11 nicht aus.
– CB Bailey
28. Mai 2012 um 11:34 Uhr
Wenn es nicht undefiniert wäre, könnte es definitiv nicht sein
10
es wäre entweder01
oder00
. (c++
wird immer den Wert auswertenc
hatte Vor inkrementiert wird). Und selbst wenn es nicht undefiniert wäre, wäre es immer noch schrecklich verwirrend.– linksherum
28. Mai 2012 um 11:38 Uhr
Weißt du, als ich den Titel „cout << c++ << c“ las, dachte ich kurz daran als eine Aussage über die Beziehung zwischen den Sprachen C und C++ und einer anderen namens „cout“. Weißt du, wie jemand gesagt hat, dass er dachte, dass „cout“ C++ viel unterlegen sei und dass C++ C viel unterlegen sei – und wahrscheinlich aufgrund der Transitivität, dass „cout“ sehr, sehr viel schlechter als C. 🙂
– tchrist
28. Mai 2012 um 19:36 Uhr