Czy i = (i = 6) to UB?

0

Witam,

standard c++ z 2003 roku taką treść zawiera:
1.9.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 evaluations7)
shall be complete and no side effects of subsequent evaluations shall have taken place.

1.9.12

A full-expression is an expression that is not a subexpression of another expression. If a language construct
is defined to produce an implicit call of a function, a use of the language construct is considered to be an
expression for the purposes of this definition.

full-expression to:

i = (i = 6)

sub-expression to:

i = 6

Między full a sub expression nie ma sequence point, a mamy tu modyfikację obiektu, więc na podstawie 1.9.7 wnioskuję, że jest to UB. Dobrze myślę?

0

Nie rzucę żadnym numerkiem ze standardu, ale uważam że to UB.

0

Wydaje mi się, że to może być poprawna operacja.

The side effect (modification of the left argument) of the built-in assignment operator and of all built-in compound assignment operators is sequenced after the value computation (but not the side effects) of both left and right arguments, and is sequenced before the value computation of the assignment expression (that is, before returning the reference to the modified object)

Podkreślenie moje.

10

Afaik w C++03 to UB, w C++11,14,17 nie. Zedytuję z cytatami.

C++03: UB
Istotne są:

ISO/IEC 14882:2003 §1.9 [intro.execution]/7 napisał(a)

Accessing an object designated by avolatilelvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are allside 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 calledsequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.

oraz:

ISO/IEC 14882:2003 §5 [expr]/4 napisał(a)

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. 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.

operator= nie wprowadza nowego sequence pointa, więc dwukrotny zapis do tej samej zmiennej to UB.

C++11 (i 14): UB, z innego powodu

n3337 §1.9 [intro.execution]/15 napisał(a)

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] 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.

Sequence points zostały zastąpione sequenced after i sequenced before.

n3337 §5.17 [expr.ass] napisał(a)

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

czyli dla i = i = 42, co jest równoznaczne z zapisem i = (i = 42), wyrażenie i = (i = 42) zostanie wykonane dopiero po zakończeniu obliczania i i (i = 42). Jednak obczenia i i (i = 42) są wzajemnie unsequenced. co jednak nie ma się nijak do zacytowanego wyżej punktu standardu, bo tylko jedno z nich ma side-effect.
Ponieważ jedno ma side-effect a drugie to value computation jest to UB.

C++17: well-defined

n4606 §5.18 [expr.ass]/1 napisał(a)

The result in all cases is a bit-field if the left operand is a bit-field. In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. The right operand is sequenced before the left operand.

Operandy są explicite obliczane w zdefiniowanej kolejności.

Dodatkowo, od C++17 te same zasady obowiązują przeładowane operatory: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0145r3.pdf

3

Co do C++11 i nowszych.

kq napisał(a):

Afaik w C++03 to UB, w C++11,14,17 nie. Zedytuję z cytatami.
C++11 (i wyżej):

n3337 §1.9 [intro.execution] napisał(a)

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] 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.

Sequence points zostały zastąpione sequenced after i sequenced before.

n3337 §5 [expr.ass]/15 napisał(a)

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.

czyli dla i = i = 42, co jest równoznaczne z zapisem i = (i = 42), wyrażenie i = (i = 42) zostanie wykonane dopiero po zakończeniu obliczania i i (i = 42). Jednak obczenia i i (i = 42) są wzajemnie unsequenced - co jednak nie ma się nijak do zacytowanego wyżej punktu standardu, bo tylko jedno z nich ma side-effect.

Właśnie wnioskowałbym z zacytowanego przez Ciebie tekstu, że to UB.

  1. a) Obliczenie (i=1)
  2. b) Obliczenie i
  3. wykonanie przypisania = // i=(i=1)

Natomiast 1a i 1b są względem siebie //unsequenced/, gdyż

n3337 §1.9 [intro.execution] napisał(a)

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

0

W tym momencie zaczynam rozumieć dlaczego Rust zdecydował, że przypisanie będzie zwracać () (odpowiednik void). Zawsze.

w C# jakoś sobie poradzili bez UB (jakiekolwiek potencjalne sytuacje mogące prowadzić do UB są możliwe tylko wewnątrz bloków unsafe).
Wszelkie dziwne operacje typu i = (i = 6) czy niesławne i = i++ są albo:

  • błędem, który się nie kompiluje, albo
  • jeśli się kompilują, to mają wynik ściśle zdefiniowany.

Przykładowo, w C# w przypadku int i = 1; i = i++ + i++; zdefiniowany wynik to 3.

0
Azarien napisał(a):

W tym momencie zaczynam rozumieć dlaczego Rust zdecydował, że przypisanie będzie zwracać () (odpowiednik void). Zawsze.

w C# jakoś sobie poradzili bez UB (jakiekolwiek potencjalne sytuacje mogące prowadzić do UB są możliwe tylko wewnątrz bloków unsafe).
Wszelkie dziwne operacje typu i = (i = 6) czy niesławne i = i++ są albo:

  • błędem, który się nie kompiluje, albo
  • jeśli się kompilują, to mają wynik ściśle zdefiniowany.

Przykładowo, w C# w przypadku int i = 1; i = i++ + i++; zdefiniowany wynik to 3.

To nie jedyny powód. Dodatkowym powodem, dla którego Rust zwraca wartość pustą po przypisaniu, jest move-by-defaut. Bo w takim przypadku bardzo łatwo można byłoby zrobić błąd w stylu a = b = NoCopy i wtedy ciężko określić czas życia oraz wartości poszczególnych zmiennych.

0

Zastanawiam się jeszcze co to znaczy value computation w przypadku samego i będącego po lewej stronie operatora przypisania?

0

value computation to także pobranie adresu aby utworzyć glvalue. Standard opisuje to tak:

n3337 §1.9 [intro.execution]/12 napisał(a)

Evaluation of an expression (or a sub-expression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects.

Tak więc przy zapisie a = b odbywa się value computation zarówno a jak i b.

0

Po lewej stronie może być funkcja zwracająca referencję, i wywołanie tej funkcji to byłoby właśnie "value computation".
W przypadku zwykłego int i uznaj że tego etapu nie ma, ewentualnie że jest nim pobranie referencji do i.

0

Ok dzięki za odpowiedzi i dyskusję :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1