18

Speaking in the context of the C++11 standard (which no longer has a concept of sequence points, as you know) I want to understand how two simplest examples are defined.

int i = 0;

i = i++;   // #0

i = ++i;   // #1

There are two topics on SO which explain those examples within the C++11 context. Here it was said that #0 invokes UB and #1 is well-defined. Here it was said that both examples are undefined. This ambiguity confuses me much. I've read this well-structured reference three times already but the topic seems to be way too complicated for me.

.

Let's analyze the example #0: i = i++;.

Corresponding quotes are:

  • The value computation of the built-in postincrement and postdecrement operators is sequenced before its side-effect.

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

  • If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.

As I get it, the side effect of the assignment operator is not sequenced with side effects of it's left and right arguments. Thus the side effect of the assignment operator is not sequenced with the side effects of i++. So #0 invokes an UB.

.

Let's analyze the example #1: i = ++i;.

Corresponding quotes are:

  • The side effect of the built-in preincrement and predecrement operators is sequenced before its value computation (implicit rule due to definition as compound assignment)

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

  • If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.

I can not see, how this example is different from the #0. This seems to be an UB for me for the very same reason as #0. The side effect of assignment is not sequenced with the side effect of ++i. It seems to be an UB. The topic liked above says it is well-defined. Why?

.

Question: how can I apply quoted rules to determine the UB of the examples. An as simple as possible explanation would be greatly appreciated. Thank you!

Community
  • 1
  • 1
Kolyunya
  • 5,973
  • 7
  • 46
  • 81
  • 5
    I would have a pretty frank conversation with the programmer if I saw this in production code. Yes it is an intellectual curiorisity but can't see much merit in thinking about this too hard. That, said, +1 for the well-written question and I will read the accepted answer in detail. – Bathsheba Jul 01 '13 at 08:29
  • 2
    @Bathsheba I agree that those code examples should probably never be in a real code, but I think the understanding of the standard will help to determine the UB in some other examples. – Kolyunya Jul 01 '13 at 08:32
  • I upvoted you on the quality of the question and, like you, await an answer of similar quality. – Bathsheba Jul 01 '13 at 08:33
  • @Bathsheba thank you, hope we will see some comprehensive explanation here. – Kolyunya Jul 01 '13 at 08:36
  • After the discussion is done please someone add it to the community answer – Balog Pal Jul 01 '13 at 09:01

2 Answers2

9

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

Kobi
  • 135,331
  • 41
  • 252
  • 292
MWid
  • 4,429
  • 3
  • 20
  • 20
  • Thank you for a detailed answer. If I get it right fully described evaluation order of the second example is: compute arguments of inner assignment ==> side effects of inner assignment ==> compute value of inner assignmnet ==> side effects of outer assignment ==> compute value of outer assignment. – Kolyunya Jul 01 '13 at 11:47
  • @Kolyunya I updated my answer. I hope this answers your question. – MWid Jul 01 '13 at 12:18
  • Thank you. I think I did get it. – Kolyunya Jul 01 '13 at 12:29
  • @MWid I think that thers's no value computation of left hand side `i` because the value computation of left operand and right operand are indetermined sequenced,which is before or after ? So the modification to "++i" and the value computation of i(the left hand) are unsequenced ,it would invoke UB,but actually "i=++i" is well-formed,so I think there's no value computation of "i" (the left hand) – xmh0511 Mar 11 '20 at 02:58
  • @xmh0511 This exact same problem troubled me until now, I hope. The thing is that with assignment operator (=), the evaluation of RHS is sequenced before LHS. So in `i = i++;` case we have undefined behaviour because the side-effect of RHS is sequenced after value computation of RHS meaning that side-effect of RHS is unsequenced with LHS which means we got UB. In `i = ++i;` the side-effect is sequenced before value computation of RHS which makes valid C++ code. – domdrag Nov 29 '22 at 18:10
  • @xmh0511 For even more complicated case like `i = i++ + 1;` (which is also UB) I suggest reading https://stackoverflow.com/questions/10778627/why-is-i-i-1-undefined-behavior-in-c11. The answers explain it really well. My explanation is the comment above isn't really good; my rationales were described vaguely. – domdrag Nov 29 '22 at 18:23
8

The key difference is that ++i is defined as i += 1, so

i = ++i;

is the same as:

i = (i += 1);

Since the side effects of the += operator are sequenced before the value computation of the operator, the actual modification of i in ++i is sequenced before the outer assignment. This follows directly from the sections you quote: "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)"

This is due to the nested assignment operator; the (outer) assignment operator only imposes sequenced before on the value computation of its operands, not on their side effects. (But of course, it doesn't undo sequencing imposed otherwise.)

And as you indirectly point out, this is new to C++11; previously, both were undefined. The older versions of C++ used sequence points, rather than sequenced before, and there was no sequence point in any of the assignment operators. (I have the impression that the intent was that operators which result in an lvalue have a value which is sequenced after any side effects. In earlier C++, the expression *&++i was undefined behavior; in C++11, it is guaranteed to be the same as ++i.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 1
    Thank you for an answer James. Could you please explain why this is true, probably with some references? `Since the side effects of the += operator are sequenced before the value computation of the operator, the actual modification of i in ++i is sequenced before the outer assignment.` – Kolyunya Jul 01 '13 at 08:45
  • 1
    The quote in my question states that the side effect of the first assignment in `i = (i += 1);` is not sequenced with the side effect of it's right argument - `(i += 1)`. Do I misunderstand it? – Kolyunya Jul 01 '13 at 08:51
  • 1
    I do not know if it is only me who is not smart enough to understand it, but probably some very detailed explanation would help me and some other folks. – Kolyunya Jul 01 '13 at 08:53
  • The quote in your question says that the (outer) assignment operator does not impose any sequencing with regards to the side effects of its left and right hand expressions. All the outer assignment operator ensures is that the evaluation of the two values is sequenced before the assignment. The inner assignment guarantees that the side effect is sequenced before the value evaluation. Sequenced before is transitive: a "isSequencedBefore" b and b "isSequencedBefore" c implies a "isSequencedBefore" c. – James Kanze Jul 01 '13 at 09:20
  • James, I did finally got the part of your answer concerning the sequence order. Thank you in advance! – Kolyunya Jul 01 '13 at 11:40
  • *"I have the impression that the intent was that operators which result in an lvalue have a value which is sequenced after any side effects"* - actually, this *is* codified even in C++03. It says *"The result of the assignment operation is the value stored in the left operand **after** the assignment has taken place; the result is an lvalue."*. – Johannes Schaub - litb Jul 01 '13 at 21:09
  • Note that we are allowed to both read and modify the value of an object without an intervening sequence point. But only if we read the result of the modification (it just says *"Furthermore, the **prior** value shall be accessed only to determine the value to be stored."* - this doesn't forbid reading the result of the modification). Granted, this is all very subtle, but can be interpreted to make `*&++i` well-formed. – Johannes Schaub - litb Jul 01 '13 at 21:14
  • @JohannesSchaub-litb The result is the value after the assignment has taken place, but the side effect of the value changing in memory is unordered (no sequence point) with relationship to the assignment in C++03. – James Kanze Jul 02 '13 at 07:53
  • @james that is true but it is not relevant, because there is no rule that makes it undefined behavior. – Johannes Schaub - litb Jul 02 '13 at 10:07
  • @JohannesSchaub-litb In C++03, there is no sequence point in the expression `i = ++i`. `i` is modified twice without an intervening sequence point, so the behavior is undefined. – James Kanze Jul 02 '13 at 10:25
  • 4
    @JamesKanze i am not about that, but about `*&++i`. That doesn't modify `i` twice. – Johannes Schaub - litb Jul 02 '13 at 11:11
  • 3
    @JohannesSchaub-litb A follow-up question on `*&++i` being undefined pre-C++11: https://stackoverflow.com/questions/28465121/does-i-cause-undefined-behaviour-in-c03 – Deduplicator Feb 11 '15 at 21:56