HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppCritical

Why is f(i = -1, i = -1) undefined behavior?

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
undefinedwhybehavior

Problem

I was reading about order of evaluation violations, and they give an example that puzzles me.


1) If a side effect on a scalar object is un-sequenced relative to another side effect on the same scalar object, the behavior is undefined.

// snip
f(i = -1, i = -1); // undefined behavior


In this context, i is a scalar object, which apparently means


Arithmetic types (3.9.1), enumeration types, pointer types, pointer to member types (3.9.2), std::nullptr_t, and cv-qualified versions of these types (3.9.3) are collectively called scalar types.

I don’t see how the statement is ambiguous in that case. It seems to me that regardless of if the first or second argument is evaluated first, i ends up as -1, and both arguments are also -1.

Can someone please clarify?

UPDATE

I really appreciate all the discussion. So far, I like @harmic’s answer a lot since it exposes the pitfalls and intricacies of defining this statement in spite of how straight forward it looks at first glance. @acheong87 points out some issues that come up when using references, but I think that's orthogonal to the unsequenced side effects aspect of this question.

SUMMARY

Since this question got a ton of attention, I will summarize the main points/answers. First, allow me a small digression to point out that "why" can have closely related yet subtly different meanings, namely "for what cause", "for what reason", and "for what purpose". I will group the answers by which of those meanings of "why" they addressed.

for what cause

The main answer here comes from Paul Draper, with Martin J contributing a similar but not as extensive answer. Paul Draper's answer boils down to


It is undefined behavior because it is not defined what the behavior is.

The answer is overall very good in terms of explaining what the C++ standard says. It also addresses some related cases of UB such as f(++i, ++i); and f(i=1, i=-1);. In the first of the related cases, it's not clear if the first arg

Solution

Since the operations are unsequenced, there is nothing to say that the instructions performing the assignment cannot be interleaved. It might be optimal to do so, depending on CPU architecture. The referenced page states this:


If A is not sequenced before B and B is not sequenced before A, then
two possibilities exist:



-
evaluations of A and B are unsequenced: they may be performed in any order and may overlap (within a single thread of execution, the
compiler may interleave the CPU instructions that comprise A and B)

-
evaluations of A and B are indeterminately-sequenced: they may be performed in any order but may not overlap: either A will be complete
before B, or B will be complete before A. The order may be the
opposite the next time the same expression is evaluated.


That by itself doesn't seem like it would cause a problem - assuming that the operation being performed is storing the value -1 into a memory location. But there is also nothing to say that the compiler cannot optimize that into a separate set of instructions that has the same effect, but which could fail if the operation was interleaved with another operation on the same memory location.

For example, imagine that it was more efficient to zero the memory, then decrement it, compared with loading the value -1 in. Then this:

f(i=-1, i=-1)


might become:

clear i
clear i
decr i
decr i


Now i is -2.

It is probably a bogus example, but it is possible.

Code Snippets

f(i=-1, i=-1)
clear i
clear i
decr i
decr i

Context

Stack Overflow Q#21670459, score: 359

Revisions (0)

No revisions yet.