2

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.

aka.nice
  • 9,100
  • 1
  • 28
  • 40
  • I avoided using both C and C++ tags because not (exactly) the same language, but would be interested to know why the evaluation order rules would differ in C and C++ in this case. – aka.nice Sep 17 '21 at 12:47
  • Not related but I always wander why someone is writing this kind of code? Saving the keyboard? – 0___________ Sep 17 '21 at 12:58
  • @0___________: I don't program in C but see "clever" constructs like that in other languages as well. And usually it's developers trying to be clever. Either because they like to be viewed as clever or because they just like reading clever code. Unfortunately for them it's almost always better to write boring code that works. – Joachim Sauer Sep 17 '21 at 13:09
  • @0___________ one never can tell the motivation, but maybe it's reasonning at a lower level, increment a register and keep the last bit. Don't forget that C was born as a generic assembler. Ask why the hell the language provides those ++ operator at all? So as to create complex rules that prevents casual programming? – aka.nice Sep 17 '21 at 13:48

3 Answers3

5

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;
dbush
  • 205,898
  • 23
  • 218
  • 273
  • So must I understand that ++i will take (i+1) as value, but that the C compiler may well defer assignment (i = i+1) after the evaluation of other subexpressions? Does it differ from `index = (index = index + 1) & 1;` ? – aka.nice Sep 17 '21 at 12:59
  • @aka.nice That example has the same problem. The subexpression `index = index + 1` has the value of `index + 1` but the update to `index` could happen at any time before the next sequence point. – dbush Sep 17 '21 at 13:01
3

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Yes exactly, what is obvious for post-increment (taking the value, and incrementing the variable are two different operations) is not obvious for pre-increment and caused my confusion.. Especially when viewing it as single increment register instruction like in old cpu architectures. – aka.nice Sep 17 '21 at 13:21
2

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.

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • Yes, I like this explanation as it clearly decompose the operations taking the incremented value and incrementing the variable. It is obvious for post-increment, but note that obvious for pre-increment. – aka.nice Sep 17 '21 at 13:14
  • Hard to select an answer, this one is my favourite for breviety, despite not citing the standard, but in fact, the 3 are good and complementary. – aka.nice Sep 17 '21 at 20:54