clanguage-lawyersequencec99c17

Undefined behaviour in C in i=i++; but what about i=++i;?


I have studied sequence points (C99) and sequenced before /unsequenced (C17) and some other posts here in SO about this topic and its relation with undefined behaviour.

I think with C99 its ultra-clear that i=i++; and i=++i; both result in undefined behaviour, but according to C17 (or even C23) we have this:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.

And therefore, this:

i=i++;

Results in undefined behaviour because we have here to side effects the postfix ++ operator and the assignment, and according to the answer of haccks in this post Why are these constructs using pre and post-increment undefined behavior?, we know that both side effects are unsequenced relative to each other so this results in undefined behaviour, but what about this:

i=++i;

At least for what I know here the incremented value of i should be used in the full expression, so we again have two side effects but now the side effect result of the pre-increment operator must be sequenced before the side effect result of the assignment operator, right?

And now my theory why, according to C11/C17/C23, "i=++i; is undefined behaviour is because the side effect result of the pre-increment operator is unsequenced relative to the value computation of "i" in the LHS right?


Solution

  • From https://port70.net/~nsz/c/c11/n1570.html#6.5.3.1p2 or https://port70.net/~nsz/c/c99/n1256.html#6.5.3.1p2 :

    The value of the operand of the prefix ++ operator is incremented. The result is the new value of the operand after incrementation. The expression ++E is equivalent to (E+=1). [...]

    From https://port70.net/~nsz/c/c11/n1570.html#6.5.16.2p3 or https://port70.net/~nsz/c/c99/n1256.html#6.5.16.2p3 :

    A compound assignment of the form E1 op = E2 is equivalent to the simple assignment expression E1 = E1 op (E2), except that the lvalue E1 is evaluated only once [...]

    So we have i = (i = i + 1);, where the two i are evaluated once.

    From https://port70.net/~nsz/c/c11/n1570.html#6.5.16p3 or https://port70.net/~nsz/c/c99/n1256.html#6.5.16p3 :

    An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment,111) but is not an lvalue. The type of an assignment expression is the type the left operand would have after lvalue conversion. The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced.

    Now we start the engine. Let's mark them i₁ = (i₂ = i₃ + 1); just to know to which i I am referring to.

    So updating i₂ is sequenced after computation i₃ + 1.

    And updating i₁ is sequenced after computation i₂ = i₃ + 1.

    But! Updating i₁ and updating i₂ are unsequenced. We do not know "when" the side effect of updating i₂ will happen relative to the value computation of the value of the expression i₂ = i₃ + 1. i₂ could be updated right after, or could be updated at the next sequence point, which is at ;.

    But there is no sequence point after i₂ = i₃ + 1. Updating of i₁ is after computing i₂ = i₃ + 1, but is not sequenced with updating i₂. At the time of updating i₁ the side effect of updating i₂ is still "hanging", "to do". Because updating i₁ and i₂ is unsequenced, this is why it's undefined behavior.