Operation mit Seiteneffekt in einem return-Ausdruck



  • volkard schrieb:

    Gugelmoser schrieb:

    Wieso wird die Priorität hierbei nicht beachtet?

    Priorität ist nur, wierum unsichtbare Klammen stehen.
    Das beeinflußt NICHT die zeitliche Auswertungsreihenfolge.
    x=a()+b()*c();
    Hier wird vielleicht zuerst a, dann c und dann erst b aufgerufen und die Ergebnisse zwischengepeichert. Zeitliche Reihenfolge undefiniert.
    Und dann wird das * vor dem + ausgeführt. Prioritäten definiert.

    Ich verstehe es immer noch nicht ganz. Wo kommt z.B. die Priorität des Funktionsaufrufes jemals zum Tragen?

    Gibt es dazu eine allgemeine Regel, dass man nicht in eine Falle tappt (bzgl. Seiteneffekt und Auswertungsreihenfolge)?


  • Mod

    Gugelmoser schrieb:

    Gibt es dazu eine allgemeine Regel, dass man nicht in eine Falle tappt (bzgl. Seiteneffekt und Auswertungsreihenfolge)?

    Ja. Sequenzpunkte/sequence points. C++ Standard Kapitel 1.9 wird das sogar recht verständlich erklärt, was das genau bedeutet und wo die sind.



  • Gugelmoser schrieb:

    Wo kommt z.B. die Priorität des Funktionsaufrufes jemals zum Tragen?

    cout<<sin(5);
    ist
    cout<<(sin(5))
    und nicht
    (cout<<sin)(5)

    oder

    #include <iostream>
    #include <cmath>
    using namespace std;
    
    double hello()
    {
        cout<<"hello";
        return 5;
    }
    double world()
    {
        cout<<"world";
        return 3;
    }
    
    int main()
    {
        typedef double funcType();
        funcType* h=&hello;
        funcType* w=&world;
        //cout<<*h()<<'\n';//Klappt nicht, denn () bindet stärker als *.
        cout<<(*h)()<<'\n';//Klappt.
        cout<<(*h)()+(*w)()<<'\n';//Zufällig wird h vor w aufgerufen, Glück gehabt.
        cout<<pow((*h)(),(*w)())<<'\n';//Und jetzt andersrum.
        return 0;
    }
    

    Ausgabe bei mir:

    hello5
    helloworld8
    worldhello125
    


  • Noch kurz eine off-topic Frage:

    Bedeutet undefined behavior, dass wirklich alles passieren könnte, dass ich bei folgendem

    i = i++ - 1;
    

    auch ein Bluescreen bekommen könnte?



  • Bei Undefined Behaviour darf alles passieren. Hier hast du aber einen Fall von Unspecified Behaviour.



  • Michael E. schrieb:

    Bei Undefined Behaviour darf alles passieren. Hier hast du aber einen Fall von Unspecified Behaviour.

    Um ehrlich zu sein, das hab ich auch gedacht. Aber dann hab ich gesehen, dass es im Draft (n3242) als Undefined Behaviour bezeichnet wird. [in früheren Drafts stand noch Unspecified Behaviour dran.]



  • Gugelmoser schrieb:

    Michael E. schrieb:

    Bei Undefined Behaviour darf alles passieren. Hier hast du aber einen Fall von Unspecified Behaviour.

    Um ehrlich zu sein, das hab ich auch gedacht. Aber dann hab ich gesehen, dass es im Draft (n3242) als Undefined Behaviour bezeichnet wird.

    Kapitelnummer? n3242 ist ja nicht der letzte Draft gewesen...



  • pumuckl schrieb:

    Gugelmoser schrieb:

    Michael E. schrieb:

    Bei Undefined Behaviour darf alles passieren. Hier hast du aber einen Fall von Unspecified Behaviour.

    Um ehrlich zu sein, das hab ich auch gedacht. Aber dann hab ich gesehen, dass es im Draft (n3242) als Undefined Behaviour bezeichnet wird.

    Kapitelnummer? n3242 ist ja nicht der letzte Draft gewesen...

    Kapitel 1.9, Absatz 15.



  • Gugelmoser schrieb:

    Kapitel 1.9, Absatz 15.

    Undefined auch in n3291


  • Mod

    Beispiele sind bekanntlich nicht normativ, der relevante Satz steht aber direkt davor:

    If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

    "sequenced after" ist transitiv.
    Der Seiteneffekt der Zuweisung wird nach der Wertberechung des Ausdrucks i++ - 1 sequenziert, welche wiederum nach der Wertberechnung von i++ sequenziert wird. Es besteht aber keine Sequenzierung zwischen der Wertberechnung von i++ und dem Seiteneffekt, der Erhöhung des Wertes, der in i gespeichert ist. Eine Sequenzierung der Seiteneffekte findet auch nicht aus anderen Gründen statt. Damit resultiert UB.


  • Mod

    Hier ein kleiner Quiz: bestimme den Wert des Ausdruckes und den der Variable i (unabhängig voneinander, i sei also immer 0 vor der Auswertung jedes einzelnen Ausdruckes). Im Falle von unspezifiziertem Verhalten sind alle möglichen Ergebnisse gefragt.

    int& foo(int& v) { return ++v; }
    
    int i = 0;
    i + i;              // (1)
    ++i + i;            // (2)
    i++ + i;            // (3)
    i = ++i;            // (4)
    foo(i) + i;         // (5)
    foo(i) + i++;       // (6)
    foo(++i) + i;       // (7)
    foo(++i) + i++;     // (8)
    foo(foo(i)) + i;    // (9)
    foo(++i) + foo(++i);// (10)
    

    Lösung gibts später (bei Interesse).



  • camper schrieb:

    Lösung gibts später (bei Interesse).

    Also ich habe natürlich Interesse. 🙂



  • Frage: zählt die Auswertung des Ausdrucks i als "value computation"?
    (Falls ja, hab ich 9x UB)


  • Mod

    pumuckl schrieb:

    Frage: zählt die Auswertung des Ausdrucks i als "value computation"?
    (Falls ja, hab ich 9x UB)

    Die Bestimmung des Wertes einer Variablen in einem primären Ausdruck (als Folge einer l-zu-rvalue-Konvertierung) ist auch eine "value computation", allerdings sind das ein bisschen zu viele UBs. Ich denke wir können 1-4 abhacken, (1) ist trivial, 2-4 UB (4 ist evtl. bloß unspezifiziert).
    Bei 5-10 sind nicht alle undefiniert.



  • camper schrieb:

    Bei 5-10 sind nicht alle undefiniert.

    Aber man kann sich doch gar nicht sicher sein, ob zuerst der rechte oder der linke Ausdruck ausgewertet wird.



  • Gugelmoser schrieb:

    Aber man kann sich doch gar nicht sicher sein, ob zuerst der rechte oder der linke Ausdruck ausgewertet wird.

    Ja, aber z.B. (5) ist nicht undefiniert, sondern nicht spezifiziert (hoffe ich mal ;)), weil ein Sequenzpunkt zwischen ++i und i liegt.



  • camper schrieb:

    Bei 5-10 sind nicht alle undefiniert.

    Gnarf, got it. 11, 12 und 14 sind garkein gültiges C++, weil das temporäre objekt aus i++ garnicht an int& binden kann 😛

    Ich nehme mal an, du meinst jeweils ++i.


  • Mod

    pumuckl schrieb:

    camper schrieb:

    Bei 5-10 sind nicht alle undefiniert.

    Gnarf, got it. 11, 12 und 14 sind garkein gültiges C++, weil das temporäre objekt aus i++ garnicht an int& binden kann 😛

    Ich nehme mal an, du meinst jeweils ++i.

    oh... ja, korrigiert


  • Mod

    Habe mich doch ein bisschen selbst reingelegt, allerdings gibt es auch Unterschiede zu C++03

    1. i + i

    C++11 schrieb:

    1.9/15 [...] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.

    Die Summe wird also erst berechnet, nachdem der Wert der Summanden ermittelt wurde. Eine bestimmte Reihenfolge bei der Auswert der Summanden besteht nicht, da allerdings auch keine Seiteneffekte im Spiel sind, die diese Auswertung beinflussen könnten, ist klar, dass hier nur 0 herauskommen kann, der Wert von i ändert sich nicht.

    1. ++i + i

    C+11 schrieb:

    1.9/15 [...] If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

    Die Auswertung des rechten Summanden und der Seiteneffekt der Inkrementierung sind nicht sequenziert -> UB

    1. i++ + i
      wie 2)

    2. i = ++i
      Dieser Fall ist interessant, und C++11 gibt hier eine andere Antwort als C++03 oder C99!

    C++03 schrieb:

    5/4 Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.53) Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. [Example:
    i = v[i++]; // the behavior is unspecified
    i = 7, i++, i++; // i becomes 9
    i = ++i + 1; // the behavior is unspecified
    i = i + 1; // the value of i is incremented
    —end example]

    Abgesehen davon, dass das Beispiel fehlerhaft ist: ganz klar wird hier i zweimal modifiziert, einen Sequenzpunkt gibt es erst am Ende des Ausdruckes, also resultiert UB. Der zitierte Absatz ist in C++11 ersatzlos weggefallen.
    Man könnte noch darüber nachdenken, ob das Speichern des gleichen Wertes in einem Objekt eine Modifikation darstellt:

    x = x = 0
    

    Falls nicht, besteht kein Problem, sofern irgendwie sichergestellt werden kann, dass die Modifikation von i vor der Ermittelung des Wertes des Inkrementierens stattfindet. Das Problem dabei ist, dass die Formulierung in C++03 offensichtlich von der im C-Standard abstammt:

    C++03 schrieb:

    5.3.2 Increment and decrement [expr.pre.incr]
    1 The operand of prefix ++ is modified by adding 1, or set to true if it is bool (this use is deprecated). The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type or a pointer to a completely-defined object type. The value is the new value of the operand; it is an lvalue. If x is not of type bool, the expression ++x is equivalent to x+=1. [Note: see the discussions of addition (5.7) and assignment operators (5.17) for information on conversions. ]

    Das ergibt keinen Sinn. Der Wert eines skalaren Objektes ist kein Objekt. lvalues aber verweisen auf Objekte (3.10/2). Hier passt etwas nicht. Obwohl dieser Unfall unproblematisch ist, wenn es nur darum geht, zu verstehen, was Inkrementieren macht, hilft es leider nicht, wenn es um die Reihenfolge geht. Die Formulierung bei der Zuweisung ist leider genauso fehlerhaft.
    Zusammenfassend kann man also sagen, dass der Ausdruck i = ++i möglicherweise definiert ist, sicher geht man aber, wenn man annimmt, es wäre undefiniert.

    In C++11 ist diese Sache dagegen klar:
    Falls demonstriert werden kann, dass eine klare Reihenfolge der einzelnen Elemente der Auswertung des Ausdrucks (Ermittelung des Wertes von i (1), Speichern des Erhöhten Wertes von i (2), Ermittelung des inkrementierten Wertes (3) und Speichern dieses Wertes in i (wegen der Zuweisung) (4)) besteht, dann ist das Ergebnis wohldefiniert.
    Klar ist, dass der ursprüngliche Wert von i vor Ermittelung des erhöhten Wertes und dieser wiederum vor der Speichern aufgrund der Zuweisung stattfindet, also (1)->(3)->(4); (x)->(y) soll hier "x is sequenced before y" heißen.
    Fraglich ist nur, ob eine (möglicherweise unbestimmte) Reihenfolge zwischen (2) und (4) besteht.

    C++11 schrieb:

    5.3.2 Increment and decrement [expr.pre.incr]
    1 The operand of prefix ++ is modified by adding 1, or set to true if it is bool (this use is deprecated). The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type or a pointer to
    a completely-defined object type. The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field. If x is not of type bool, the expression ++x is equivalent to x+=1 [ Note: See the
    discussions of addition (5.7) and assignment operators (5.17) for information on conversions. —end note ]

    Vergleiche

    C++03 schrieb:

    The value is the new value of the operand; it is an lvalue.

    C++11 schrieb:

    The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field.

    Der Unterschied ist subtil aber wichtig. In

    int i = 0, j = ++i;
    

    ist ganz klar, dass j mit 1 initialisiert wird. Der Ausdruck ++i verweist aber auf ein Objekt. Erst die folgende Umwandlung in ein rvalue ermittelt den gespeichterten Wert. Der gespeicherte Wert ist aber der erhöhte Werte. Das kann nur gerantiert sein, wenn die Modifikation von i vor der Ermittelung des "Wertes" (der Erzeugung des lvalues) von ++i erfolgt. Folglich gilt (3)->(2)
    "sequenced after" ist eine transitive Beziehung, wegen (3)->(2) und (2)->(4) gilt auch (3)->(4). Es besteht also eine klare Reihenfolge der Modifikation. Das Ergebnis ist wohldefiniert.

    Es sollte nicht zu sehr überraschen, dass C++11 einige Konstrukte definiert, die in C++03 noch undefiniert waren. "between sequence points" ist schließlich eine viel gröbere Gliederung als "sequenced before".
    Zu 5 ff. schreibe ich morgen etwas.



  • camper schrieb:

    C++03 schrieb:

    5/4 Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.53) Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. [Example:
    i = v[i++]; // the behavior is unspecified
    i = 7, i++, i++; // i becomes 9
    i = ++i + 1; // the behavior is unspecified
    i = i + 1; // the value of i is incremented
    —end example]

    Abgesehen davon, dass das Beispiel fehlerhaft ist: ganz klar wird hier i zweimal modifiziert, einen Sequenzpunkt gibt es erst am Ende des Ausdruckes, also resultiert UB.

    Warum ist dann i = ++i +1 nicht undefiniert?


Anmelden zum Antworten