Operation mit Seiteneffekt in einem return-Ausdruck



  • 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?


  • Mod

    Michael E. schrieb:

    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?

    bug
    Der Vergleich mit dem Beispiel in 1.9/15 in C++11 ist interessant:

    i = i++ + 1; // the behavior is undefined
    

    Ich glaube nicht, dass der Wechsel von Pre- auf Postinkrement zufällig ist, das spricht imo dafür, dass meine obige Intepretation nicht ganz daneben liegt.


  • Mod

    foo(i) + i

    C++03 schrieb:

    1.9 Program execution [intro.execution]
    [...]
    7 Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression might produce side effects. **At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.**7)
    8 Once the execution of a function begins, no expressions from the calling function are evaluated until execution of the called function has completed.8)
    [...]
    17 When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function11). Several contexts in C + + cause evaluation of a function call, even though no corresponding function call syntax appears in the translation unit. [Example: evaluation of a new expression invokes one or more allocation and constructor functions; see 5.3.4. For another example, invocation of a conversion function (12.3.2) can arise in contexts in which no function call syntax appears. ] The sequence points at function-entry and function-exit (as described above) are features of the function calls as evaluated, whatever the syntax of the expression that calls the function might be.

    5 Expressions [expr]
    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.

    Hier zeigt sich einmal mehr die manchmal etwas ungenaue Sprache im Standard. Der Definition in 1.9/7 zufolge ordnen Sequenzpunkte lediglich die Seiteneffekte der Auswertung von Ausdrücken. Der Zugriff auf den gespeicherten Wert eines Objektes ist aber kein Seiteneffekt. Nimmt man also 5./4 wörtlich, dann kann sich der hervorgehobene Teil in 5./4 nicht auf Sequenzpunkte beziehen und es wäre z.B.:

    int i = 0;
    cout << i;
    i = 1;
    

    undefiniert. Das ist nat. nicht beabsichtigt. Plausibel ist nur, anzunehmen, dass auch Wertzugriffe durch Sequenzpunkte geordnet werden. Dann garantiert aber 1.9/17, dass zwischen dem Wertzugriff bei der Auswertung des rechten Summanden und der Modifikation der Variablen in der Funktion foo garantiert ein Sequenzpunkt liegt.
    Das Verhalten ist somit unspezifiert (weil die konkrete Reihenfolge der Auswertung der Summanden nicht festgelegt ist): i ist im Anschluss 1, das Ergebnis der Auswertung entweder 1 oder 2.

    Mit C++11 kommen wir zum gleichen Ergebnis, allerdings auf weniger umständlichem Wege

    C++11 schrieb:

    1.9 Program execution [intro.execution]
    [...]
    15 Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [ Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. —end note ] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either anotherside effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
    [ Example:
    void f(int, int);
    void g(int i, int* v) {
    i = v[i++]; // the behavior is undefined
    i = 7, i++, i++; // i becomes 9
    i = i++ + 1; // the behavior is undefined
    i = i + 1; // the value of i is incremented
    f(i = -1, i = -1); // the behavior is undefined
    }
    —end example ]
    When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [ Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ] **Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.**9 Several contexts in C++ cause evaluation of a function call, even though no corresponding function call syntax appears in the translation unit. [ Example: Evaluation of a new expression invokes one or more allocation and constructor functions; see 5.3.4. For another example, invocation of a conversion function (12.3.2) can arise in contexts in which no function call syntax appears. —end example ] The sequencing constraints on the execution of the called function (as described above) are features of the function calls as evaluated, whatever the syntax of the expression that calls the function might be.

    Der vorhergehende Satz macht klar, dass hier mit Auswertung sowohl Wertberechnungen als auch Seiteneffekte gemeint sind.
    Das Ergebnis ist also wiederum unspezifiziertes Verhalten wie oben.


  • Mod

    foo(i) + i++;       // (6)
    foo(++i) + i;       // (7)
    foo(++i) + i++;     // (8)
    foo(foo(i)) + i;    // (9)
    foo(++i) + foo(++i);// (10)
    

    6: unspezifiziert, i: 1 oder 2, Summe: 1 oder 2 (Die Auswertung des Funktionsargument ist keine Wertberechnung, da nur eine Referenz initialisiert wird)
    7: undefiniert
    8: undefiniert
    9: unspezifiziert; i ist im Anschluss 2, das Ergebnis des Ausdruckes entweder 2,3 oder 4
    10: undefiniert


Anmelden zum Antworten