Operation mit Seiteneffekt in einem return-Ausdruck
-
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
-
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.
-
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
ials "value computation"?
(Falls ja, hab ich 9x UB)
-
pumuckl schrieb:
Frage: zählt die Auswertung des Ausdrucks
ials "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.
-
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
-
Habe mich doch ein bisschen selbst reingelegt, allerdings gibt es auch Unterschiede zu C++03
- 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.
- ++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
-
i++ + i
wie 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 = 0Falls 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 +1nicht undefiniert?
-
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 +1nicht undefiniert?bug
Der Vergleich mit dem Beispiel in 1.9/15 in C++11 ist interessant:i = i++ + 1; // the behavior is undefinedIch 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.
-
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.
-
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