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

Viewed 94

I have a legacy code performing double buffering with this instruction:

bufferIndex = ++bufferIndex & 1;

clang warns about unsequenced modification order of bufferIndex

warning: multiple unsequenced modifications to 'bufferIndex' [-Wunsequenced]

In the doubt, I would avoid such construct, especially since knowing that the bufferIndex has been appropriately initialized to either 0 or 1, this flip/flop could have been written more simply:

bufferIndex ^= 1;

Even without a doubt, I would avoid such pre-increment construct so that I don't cause confusion in my rewiewers mind, and so that I don't generate brainstorming warnings uselessly.
But that's not my point. My point is that I want to understand if unsequenced modification is possibly true here for my own culture.

Is the evaluation order really undefined in this case, or is the warning a bit abusive?

Note: it's not the same case as Unsequenced modification warning where the variable clearly appear twice in an unsequenced manner, nor in the other possible duplicates suggested by SO (unless I overlooked).

It's not at all obvious that this applies to this case of assignment operator = to my understanding, since expression has to be evaluated BEFORE being assigned to, and since the sole side effect is pre-increment and will necessarily happen sometime during evaluation and before assignment.

3 Answers

This statement still triggers undefined behavior.

None of the operators involved (=, binary &, prefix ++) introduce a sequence point, and both the = and ++ operators update one of their operands as a side effect. And because bufferIndex is being modified by a side effect multiple times without a sequence point, we have undefined behavior.

This is spelled out in section 6.5p2 of the C standard:

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

And in fact footnote 84 referenced above gives an almost identical example to yours:

84)) This paragraph renders undefined statement expressions such as

i = ++i + 1;
a[i++] = i;

while allowing

i = i + 1; 
a[i] = i;

Is the evaluation order really undefined in this case, or is the warning a bit abusive?

The relevant rule is 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.

(C17, paragraph 6.5/2)

Pursuant to that, we also have

The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.

(C17, paragraph 6.5/1)

Note that only the value computations are constrained by that, not the application of side effects.

Of the assignment operator, we have:

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.

(C17, paragraph 6.5.16/3)

Note that that sequences the assignment's side effect relative to the value computations of its left and right operands, but not relative to any other side effects.

There is also a sequence point at the terminating semicolon, meaning that all the value computations and side effects of the code preceding are sequenced before all those of the code following.

There are no other sequencing constraints on the given statement expression. In particular, the arithmetic and operator (&) does not add any sequencing constraints, nor does the pre-increment operator (prefix ++).

This last may be what has you confused. The pre-increment operator both computes a result (equal the sum of the stored value of its operand plus 1) and has a side effect of updating the stored value of the object designated by its operand. Although you might think about it in terms of updating the variable and then evaluating to the resulting value, that ordering is neither required nor specified.

None of the applicable constraints sequence the side effect of the assignment on variable bufferIndex relative to the side effect of the pre-increment on that same variable, therefore the behavior is undefined.

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.

Related