Undefiniertes Verhalten und Sequenzpunkte neu geladen

84

Betrachten Sie dieses Thema als Fortsetzung des folgenden Themas:

Vorherige Rate
Undefiniertes Verhalten und Sequenzpunkte

Lassen Sie uns diesen lustigen und verschlungenen Ausdruck noch einmal betrachten (die kursiven Sätze stammen aus dem obigen Thema * smile *):

i += ++i;

Wir sagen, dies ruft undefiniertes Verhalten hervor. Ich nehme an, wenn wir dies sagen, nehmen wir implizit an, dass der Typ von ieiner der eingebauten Typen ist.

Was passiert , wenn die Art der iein benutzerdefinierter Typ? Angenommen, der Typ wird Indexspäter in diesem Beitrag definiert (siehe unten). Würde es immer noch undefiniertes Verhalten hervorrufen?

Wenn ja, warum? Ist es nicht gleichbedeutend mit Schreiben i.operator+=(i.operator++());oder sogar syntaktisch einfacher i.add(i.inc());? Oder rufen auch sie undefiniertes Verhalten hervor?

Wenn nein, warum nicht? Immerhin wird das Objekt zwischen aufeinanderfolgenden Sequenzpunkten zweimali geändert . Bitte erinnern Sie sich an die Faustregel: Ein Ausdruck kann den Wert eines Objekts nur einmal zwischen aufeinanderfolgenden "Sequenzpunkten ändern . Und wenn es sich um einen Ausdruck handelt, muss er undefiniertes Verhalten aufrufen. Wenn ja, dann seine Äquivalente und muss auch undefiniertes Verhalten aufrufen, das scheint falsch zu sein! (soweit ich verstehe)i += ++ii.operator+=(i.operator++());i.add(i.inc());

Oder i += ++iist nicht ein Ausdruck von Anfang an? Wenn ja, was ist es dann und wie wird der Ausdruck definiert ?

Wenn es ein Ausdruck ist, und zugleich wird ihr Verhalten auch gut definiert, dann bedeutet dies , dass die Anzahl der Sequenzpunkte mit einem Ausdruck zugeordnet ist irgendwie auf das hängt Art des Operanden im Ausdruck beteiligt. Bin ich richtig (auch nur teilweise)?


Übrigens, wie wäre es mit diesem Ausdruck?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

Sie müssen dies auch in Ihrer Antwort berücksichtigen (wenn Sie das Verhalten sicher kennen). :-)


Ist

++++++i;

in C ++ 03 gut definiert? Immerhin ist dies das,

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};
Nawaz
quelle
13
+1 tolle Frage, die tolle Antworten inspirierte. Ich denke, ich sollte sagen, dass es immer noch schrecklicher Code ist, der überarbeitet werden sollte, um besser lesbar zu sein, aber das wissen Sie wahrscheinlich trotzdem :)
Philip Potter
4
@Was ist die Frage: Wer hat gesagt, dass es das gleiche ist? oder wer hat gesagt, dass es nicht dasselbe ist? Kommt es nicht darauf an, wie Sie sie implementieren? (Hinweis: Ich gehe davon aus, dass der Typ ein sbenutzerdefinierter Typ ist!)
Nawaz
5
Ich sehe kein skalares Objekt, das zweimal zwischen zwei Sequenzpunkten geändert wird ...
Johannes Schaub - litb
3
@Johannes: dann geht es um skalare Objekte. Was ist es? Ich frage mich, warum ich noch nie davon gehört habe. Vielleicht, weil die Tutorials / C ++ - FAQ es nicht erwähnen oder nicht betonen? Unterscheidet es sich von Objekten des eingebauten Typs?
Nawaz
3
@Phillip: Natürlich werde ich solchen Code im wirklichen Leben nicht schreiben. Tatsächlich wird es kein vernünftiger Programmierer schreiben. Diese Fragen werden normalerweise so gestellt, dass wir das gesamte Geschäft mit undefiniertem Verhalten und Sequenzpunkten besser verstehen können! :-)
Nawaz

Antworten:

48

Es sieht aus wie der Code

i.operator+=(i.operator ++());

Funktioniert einwandfrei in Bezug auf Sequenzpunkte. In Abschnitt 1.9.17 des C ++ ISO-Standards wird dies über Sequenzpunkte und Funktionsbewertung gesagt:

Beim Aufrufen einer Funktion (unabhängig davon, ob die Funktion inline ist oder nicht) gibt es nach der Auswertung aller Funktionsargumente (falls vorhanden) einen Sequenzpunkt, der vor der Ausführung von Ausdrücken oder Anweisungen im Funktionskörper erfolgt. Es gibt auch einen Sequenzpunkt nach dem Kopieren eines zurückgegebenen Werts und vor der Ausführung von Ausdrücken außerhalb der Funktion.

Dies würde zum Beispiel anzeigen, dass der i.operator ++()als Parameter zu operator +=haben nach seiner Auswertung einen Sequenzpunkt hat. Kurz gesagt, da überladene Operatoren Funktionen sind, gelten die normalen Sequenzierungsregeln.

Tolle Frage übrigens! Ich mag es wirklich, wie Sie mich zwingen, alle Nuancen einer Sprache zu verstehen, von der ich bereits dachte, dass ich sie kenne (und dachte, dass ich dachte, dass ich sie kenne). :-)

templatetypedef
quelle
12

http://www.eelis.net/C++/analogliterals.xhtml Analoge Literale kommen mir in den Sinn

  unsigned int c = ( o-----o
                     |     !
                     !     !
                     !     !
                     o-----o ).area;

  assert( c == (I-----I) * (I-------I) );

  assert( ( o-----o
            |     !
            !     !
            !     !
            !     !
            o-----o ).area == ( o---------o
                                |         !
                                !         !
                                o---------o ).area );
Industrielles Antidepressivum
quelle
Es gab eine Frage, ist ++++++ i; in C ++ 03 gut definiert?
Industrie-Antidepressivum
11

Wie bereits erwähnt, i += ++ifunktioniert Ihr Beispiel mit dem benutzerdefinierten Typ, da Sie Funktionen aufrufen und Funktionen Sequenzpunkte umfassen.

Auf der anderen Seite a[++i] = iist es nicht so glücklich anzunehmen, dass dies aIhr grundlegender Array-Typ oder sogar ein benutzerdefinierter ist. Das Problem, das Sie hier haben, ist, dass wir nicht wissen, welcher Teil des Ausdrucks izuerst ausgewertet wird. Es könnte sein, dass ++iausgewertet, an operator[](oder die Rohversion) übergeben wird, um das Objekt dort abzurufen, und dann der Wert von ian das übergeben wird (was ist, nachdem ich inkrementiert wurde). Andererseits wird möglicherweise zuerst die letztere Seite ausgewertet, für eine spätere Zuordnung gespeichert und dann das ++iTeil ausgewertet.

Edward Strange
quelle
Also ... ist das Ergebnis daher nicht spezifiziert und nicht UB, da die Reihenfolge der Auswertung von Ausdrücken nicht spezifiziert ist?
Philip Potter
@Philip: nicht angegeben bedeutet, dass wir erwarten, dass der Compiler das Verhalten angibt, während undefiniert keine solche Verpflichtung darstellt. Ich denke, es ist hier undefiniert, Compilern mehr Raum für Optimierungen zu geben.
Matthieu M.
@Noah: Ich habe auch eine Antwort gepostet. Bitte probieren Sie es aus und teilen Sie mir Ihre Gedanken mit. :-)
Nawaz
1
@Philip: Das Ergebnis ist UB, aufgrund der Regel in 5/4: "Die Anforderungen dieses Absatzes müssen für jede zulässige Reihenfolge der Unterausdrücke eines vollständigen Ausdrucks erfüllt sein; andernfalls ist das Verhalten undefiniert." Wenn alle zulässigen Bestellungen Sequenzpunkte zwischen der Änderung ++iund dem Lesen iauf der rechten Seite der Zuordnung hätten, wäre die Reihenfolge nicht spezifiziert. Da eine der zulässigen Ordnungen diese beiden Dinge ohne dazwischenliegenden Sequenzpunkt ausführt, ist das Verhalten undefiniert.
Steve Jessop
1
@Philip: Es definiert nicht nur nicht spezifiziertes Verhalten als undefiniertes Verhalten. Auch wenn der Bereich nicht spezifiziert Verhalten einige enthält , die nicht definiert ist, dann das allgemeine Verhalten ist nicht definiert. Wenn der Bereich des nicht spezifizierten Verhaltens in allen Möglichkeiten definiert ist, ist das Gesamtverhalten nicht spezifiziert. Aber Sie haben Recht mit dem zweiten Punkt, ich dachte an eine benutzerdefinierte aund integrierte i.
Steve Jessop
8

Ich denke, es ist gut definiert:

Aus dem C ++ Standardentwurf (n1905) §1.9 / 16:

"Es gibt auch einen Sequenzpunkt nach dem Kopieren eines zurückgegebenen Werts und vor der Ausführung von Ausdrücken außerhalb der Funktion13). Mehrere Kontexte in C ++ bewirken die Auswertung eines Funktionsaufrufs, obwohl in der Übersetzungseinheit keine entsprechende Funktionsaufrufsyntax angezeigt wird. [ Beispiel : Die Auswertung eines neuen Ausdrucks ruft eine oder mehrere Zuordnungs- und Konstruktorfunktionen auf, siehe 5.3.4. Für ein anderes Beispiel kann der Aufruf einer Konvertierungsfunktion (12.3.2) in Kontexten auftreten, in denen keine Funktionsaufrufsyntax erscheint. - end Beispiel ] Die Sequenzpunkte am Funktionseingang und am Funktionsexit (wie oben beschrieben) sind Merkmale der Funktionsaufrufe, die unabhängig von der Syntax des Ausdrucks ausgewertet werdendas ruft die Funktion auf. ""

Beachten Sie den Teil, den ich fett gedruckt habe. Dies bedeutet, dass es zwar einen Sequenzpunkt nach dem Inkrementfunktionsaufruf ( i.operator ++()), aber vor dem zusammengesetzten Zuweisungsaufruf ( i.operator+=) gibt.

Matthew Flaschen
quelle
6

In Ordung. Nachdem ich frühere Antworten durchgesehen hatte, dachte ich über meine eigene Frage nach, insbesondere über diesen Teil, den nur Noah zu beantworten versuchte, aber ich bin nicht vollständig von ihm überzeugt.

a[++i] = i;

Fall 1:

If aist ein Array vom integrierten Typ. Dann ist das, was Noah gesagt hat, richtig. Das ist,

a [++ i] = i hat nicht so viel Glück, wenn man annimmt, dass a Ihr grundlegender Array-Typ ist. oder sogar eine benutzerdefinierte . Das Problem, das Sie hier haben, ist, dass wir nicht wissen, welcher Teil des Ausdrucks, der i enthält, zuerst ausgewertet wird.

So a[++i]=iInvokes-Verhalten ist nicht definiert, oder das Ergebnis ist nicht spezifiziert. Was auch immer es ist, es ist nicht genau definiert!

PS: Im obigen Zitat Durchschlag ist natürlich meins.

Fall 2:

Wenn aes sich um ein benutzerdefiniertes Objekt handelt, das das Objekt überlastet operator[], gibt es erneut zwei Fälle.

  1. Wenn der Rückgabetyp der überladenen operator[]Funktion ein integrierter Typ ist, wird erneut ein a[++i]=iundefiniertes Verhalten aufgerufen oder das Ergebnis wird nicht angegeben.
  2. Wenn der Rückgabetyp der überladenen operator[]Funktion jedoch ein benutzerdefinierter Typ ist, ist das Verhalten von a[++i] = i(soweit ich weiß) genau definiert, da in diesem Fall a[++i]=idas Schreiben gleichbedeutend a.operator[](++i).operator=(i);ist mit , a[++i].operator=(i);. Das heißt, die Zuweisung operator=wird für das zurückgegebene Objekt von aufgerufen a[++i], was sehr genau definiert zu sein scheint, da zum Zeitpunkt der a[++i]Rückgabe ++ibereits ausgewertet wurde und das zurückgegebene Objekt die operator=Funktion aufruft , die den aktualisierten Wert von ials Argument an das Objekt zurückgibt . Beachten Sie, dass zwischen diesen beiden Aufrufen ein Sequenzpunkt liegt . Und die Syntax stellt sicher, dass zwischen diesen beiden Aufrufen keine Konkurrenz besteht, undoperator[]würde zuerst aufgerufen werden, und nacheinander würde das darin übergebene Argument ++iauch zuerst ausgewertet werden.

Stellen Sie sich das so vor, als würde someInstance.Fun(++k).Gun(10).Sun(k).Tun();jeder aufeinanderfolgende Funktionsaufruf ein Objekt eines benutzerdefinierten Typs zurückgeben. Für mich scheint diese Situation eher so zu sein: eat(++k);drink(10);sleep(k)In beiden Situationen gibt es nach jedem Funktionsaufruf einen Sequenzpunkt.

Bitte korrigieren Sie mich, wenn ich falsch liege. :-)

Nawaz
quelle
1
@Nawaz k++und ksind nicht durch Sequenzpunkte getrennt. Sie können beide vor Sunoder Funausgewertet werden. Die Sprache erfordert nur , dass Funvorher bewertet wird Sun, nicht, dass Fundie Argumente vor den Argumenten ausgewertet werden Sun. Ich erkläre das Gleiche noch einmal, ohne einen Hinweis geben zu können, also werden wir von hier aus nicht weiterkommen.
Philip Potter
1
@Nawaz: weil es nichts gibt, das einen Sequenzpunkt definiert, der sie trennt. Es gibt Sequenzpunkte vor und nach der Ausführung Sun, aber Fundas Argument ++kkann vorher oder nachher ausgewertet werden. Es gibt Sequenzpunkte vor und nach der Ausführung Fun, aber Sundas Argument kkann vorher oder nachher ausgewertet werden. Ein möglicher Fall ist daher, dass beide kund ++kvor einem Sunoder beiden Funausgewertet werden und beide vor den Funktionsaufruf-Sequenzpunkten liegen, sodass kein Sequenzpunkt zwischen kund getrennt wird ++k.
Philip Potter
1
@Philip: Ich wiederhole: Wie unterscheidet sich diese Situation von eat(i++);drink(10);sleep(i);? ... könnte man schon jetzt sagen, i++kann vorher oder nachher ausgewertet werden?
Nawaz
1
@Nawaz: Wie kann ich mich klarer machen? Im Fun / Sun-Beispiel gibt es keinen Sequenzpunkt zwischen kund ++k. Im Beispiel Essen / Trinken gibt es als Sequenzpunkt zwischen iund i++.
Philip Potter
3
@Philip: das macht überhaupt keinen Sinn. Zwischen Fun () und Sun () existiert ein Sequenzpunkt, aber zwischen ihren Argumenten existieren keine Sequenzpunkte. Es ist wie zu sagen, zwischen eat()und sleep()existiert Sequenzpunkte, aber zwischen diesen Argumenten gibt es nicht einmal einen. Wie können Argumente zu zwei durch Sequenzpunkte getrennten Funktionsaufrufen zu denselben Sequenzpunkten gehören ?
Nawaz