Unsequenced Value Computations (A.K.A Sequence Points)

Unsequenced value computations (a.k.a sequence points)

Native operator expressions are not equivalent to overloaded operator expressions. There is a sequence point at the binding of values to function arguments, which makes the operator++() versions well-defined. But that doesn't exist for the native-type case.

In all four cases, i changes twice within the full-expression. Since no ,, ||, or && appear in the expressions, that's instant UB.

§5/4:

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.

Edit for C++0x (updated)

§1.9/15:

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 another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

Note however that a value computation and a side effect are two distinct things. If ++i is equivalent to i = i+1, then + is the value computation and = is the side effect. From 1.9/12:

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.

So although the value computations are more strongly sequenced in C++0x than C++03, the side effects are not. Two side effects in the same expression, unless otherwise sequenced, produce UB.

Value computations are ordered by their data dependencies anyway and, side effects absent, their order of evaluation is unobservable, so I'm not sure why C++0x goes to the trouble of saying anything, but that just means I need to read more of the papers by Boehm and friends wrote.

Edit #3:

Thanks Johannes for coping with my laziness to type "sequenced" into my PDF reader search bar. I was going to bed and getting up on the last two edits anyway… right ;v) .

§5.17/1 defining the assignment operators says

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.

Also §5.3.2/1 on the preincrement operator says

If x is not of type bool, the expression ++x is equivalent to x+=1 [Note: see … addition (5.7) and assignment operators (5.17) …].

By this identity, ++ ++ x is shorthand for (x +=1) +=1. So, let's interpret that.

  • Evaluate the 1 on the far RHS and descend into the parens.
  • Evaluate the inner 1 and the value (prvalue) and address (glvalue) of x.
  • Now we need the value of the += subexpression.
    • We're done with the value computations for that subexpression.
    • The assignment side effect must be sequenced before the value of assignment is available!
  • Assign the new value to x, which is identical to the glvalue and prvalue result of the subexpression.
  • We're out of the woods now. The whole expression has now been reduced to x +=1.

So, then 1 and 3 are well-defined and 2 and 4 are undefined behavior, which you would expect.

The only other surprise I found by searching for "sequenced" in N3126 was 5.3.4/16, where the implementation is allowed to call operator new before evaluating constructor arguments. That's cool.

Edit #4: (Oh, what a tangled web we weave)

Johannes notes again that in i == ++i; the glvalue (a.k.a. the address) of i is ambiguously dependent on ++i. The glvalue is certainly a value of i, but I don't think 1.9/15 is intended to include it for the simple reason that the glvalue of a named object is constant, and cannot actually have dependencies.

For an informative strawman, consider

( i % 2? i : j ) = ++ i; // certainly undefined

Here, the glvalue of the LHS of = is dependent on a side-effect on the prvalue of i. The address of i is not in question; the outcome of the ?: is.

Perhaps a good counterexample is

int i = 3, &j = i;
j = ++ i;

Here j has a glvalue distinct from (but identical to) i. Is this well-defined, yet i = ++i is not? This represents a trivial transformation that a compiler could apply to any case.

1.9/15 should say

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 prvalue of the same scalar object, the behavior is undefined.

Sequence points and side effects: Quiet change in C11?

C11 (and also C++11) has completely reworked the wording of sequencing because C11 now has threads, and it had to explain what sequencing between threads that access the same data means. The intention of the committee was to keep things backward compatible to C99 for the case where there is only one thread of execution.

Let's have a look at the C99 version:

  1. Between the previous and next sequence point

  2. an object

  3. shall have

  4. its stored value modified at most once

  5. by the evaluation of an expression.

compared to the new text

If a side effect on

different terminolgie for 4, modifying the stored value

a scalar object

a restriction of the previous wording in 2. The new text only says
something about scalar objects

is unsequenced relative to either

unsequenced is a generalization of the concept in 1. that two statements
were separated by a sequence point. Think of two threads that modify
the same data without using a lock or something similar.

a different side effect on the same scalar object

the object is only allowed be modified once

or a value
computation using the value of the same scalar object,

or a read of the value may not appear concurrently to the modification

the behavior is undefined.

The "shall" in 3. is saying this implicitly. All "shall"s lead to UB if
they are not fulfilled.

New Sequence Points in C++11

The UB in those cases is based on [intro.execution]/15

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] 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 another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

For ++(++i): [expr.pre.incr]/1 states that ++i is defined as i+=1. This leads to [expr.ass]/1, which says

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.

Therefore, for ++(++i), equivalent to (i+=1)+=1, the inner assignment is sequenced before the outer assignment, and we have no UB.


[intro.execution]/15 has an example of UB:

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

The case here is a bit different (thanks to Oktalist for pointing out a pre/postfix mistake here). [expr.post.incr]/1 describes the effects of postfix increment. It states:

The value computation of the ++ expression is sequenced before the modification of the operand object.

However, there is no requirement on the sequencing of the side effect (the modification of i). Such a requirement could also be imposed by the assignment-expression. But the assignment-expression only requires the value computations (but not the side effects) of the operands to be sequenced before the assignment. Therefore, the two modifications via i = .. and i++ are unsequenced, and we get undefined behaviour.

N.B. i = (i = 1); does not have the same problem: The inner assignment guarantees the side effect of i = 1 is sequenced before the value computation of the same expression. And the value is required for the outer assignment, which guarantees that it (the value computation of the right operand (i = 1)) is sequenced before the side effect of the outer assignment. Similarly, i = ++i + 1; (equivalent to i = (i+=1) + 1;) has defined behaviour.


The comma operator is an example where the side effects are sequenced; [expr.comma]/1

Every value computation and side effect associated with the left expression is sequenced before every value computation and side effect associated with the right expression.

[intro.execution]/15 includes the example i = 7, i++, i++; (read: (i=7), i++, i++;), which is defined behaviour (i becomes 9).

Sequence Points vs Operator Precedence

The first answer in the question you linked to explains exactly what's going on. I'll try to rephrase it to make it more clear.

Operator precedence defines the order of the computation of values via expressions. The result of the expression (a++) is well understood.

However, the modification of the variable a is not part of the expression. Yes, really. This is the part you're having trouble understanding, but that's simply how C and C++ define it.

Expressions result in values, but some expressions can have side effects. The expression a = 1 has a value of 1, but it also has the side effect of setting the variable a to 1. As far as how C and C++ define things, these are two different steps. Similarly, a++ has a value and a side-effect.

Sequence points define when side effects are visible to expressions that are evaluated after those sequence points. Operator precedence has nothing to do with sequence points. That's just how C/C++ defines things.

does the following pre-increment expression really result in unsequenced modification?

There are two side effects on bufferIndex - one from the ++ operation and one from the =, and yes, they are unsequenced with respect to each other and can happen in any order:

tmp = bufferIndex + 1
bufferIndex = tmp & 1
bufferIndex = bufferIndex + 1

or

tmp = bufferIndex + 1
bufferIndex = bufferIndex + 1
bufferIndex = tmp & 1

or in any other order. They can even be executed simultaneously (either interleaved or in parallel if the system supports it).

The behavior is undefined - the compiler is not required to handle the situation in any particular way; any result is equally "correct" as far as the language is concerned.

Order of evaluation and undefined behaviour

Since your quotes are not directly from the standard, I will try to give a detailed answer quoting the relevant parts of the standard. The definitions of "side effects" and "evaluation" is found in paragraph 1.9/12:

Accessing an object designated by a volatile glvalue (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 (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.

The next relevant part is paragraph 1.9/15:

Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [...] 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 another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

Now let's see, how to apply this to the two examples.

i = i++;

This is the postfix form of increment and you find its definition in paragraph 5.2.6. The most relevant sentence reads:

The value computation of the ++ expression is sequenced before the modification
of the operand object.

For the assignment expression see paragraph 5.17. The relevant part states:

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.

Using all the information from above, the evaluation of the whole expression is (this order is not guaranteed by the standard!):

  • value computation of i++ (right hand side)
  • value computation of i (left hand side)
  • modification of i (side effect of ++)
  • modification of i (side effect of =)

All the standard guarantees is that the value computations of the two operands is sequenced before the value computation of the assignment expression. But the value computation of the right hand side is only "reading the value of i" and not modifying i, the two modifications (side effects) are not sequenced with respect to each other and we get undefined behavior.

What about the second example?

i = ++i;

The situation is quite different here. You find the definition of prefix increment in paragraph 5.3.2. The relevant part is:

If x is not of type bool, the expression ++x is equivalent to x+=1.

Substituting that, our expression is equivalent to

i = (i += 1)

Looking up the compound assignment operator += in 5.17/7 we get that i += 1 is equivalent to i = i + 1 except that i is only evaluated once. Hence, the expression in question finally becomes

i = ( i = (i + 1))

But we already know from above that the value computation of the = is sequenced after the value computation of the operands and the side effects are sequenced before the value computations of =. So we get a well-defined order of evaluation:

  1. compute value of i + 1 (and i - left hand side of inner expression)(#1)
  2. initiate side effect of inner =, i.e. modify "inner" i
  3. compute value of (i = i + 1), which is the "new" value of i
  4. initiate side effect of outer =, i.e. modify "outer" i
  5. compute value of full expression.

(#1): Here, i is only evaluated once, since i += 1 is equivalent to i = i + 1 except that i is only evaluated once (5.17/7).

Are these vector iteration forms the same?

Form 2 is broken.

i-- = array.erase(i);

Let's break that down;

i--  

This returns a copy of i, and then decrements i.

array.erase(i) 

This erases the element at i, and then returns the iterator to the element after.

(i--) = (array.erase(i));

This assigns the result of array.erase(i) to the copy returned by i--.

There is an additional complication in that certainly prior to C++11, you don't know whether the decrement was done before or after the call to array.erase(i), and that would have been undefined behaviour. Experimentally, GCC does the decrement before the erase, so you end up erasing the wrong element.

It may help to write the (nearly) equivalent code out:

    auto j = i;
--i; // Here??
auto k = array.erase(i);
--i; // Or here?
j = k;

If you want to do this, write it as:

    const auto j = i;
--i;
array.erase(j);

Which makes it much clearer what you are doing.



Related Topics



Leave a reply



Submit