1

I know this question is asked often in its version of "i = i++ +1" in which i appears twice, but my question differs in that is is specifically ONLY about the right hand side of this expression, the definedness of which is not obvious to me. I am only referring to:

i++ + 1;

cppreference.com states here that:

2) The value computations (but not the side-effects) of the operands to any operator are sequenced before the value computation of the result of the operator (but not its side-effects).

I understand this to mean that the value computation is sequenced but no statement is made about the side-effect.

[...]

4) The value computation of the built-in post-increment and post-decrement operators is sequenced before its side-effect.

It does not, however, specify that the side-effect of (in this case) the left operand is sequenced in relation to the value computation of the expression.

It further states:

If a side effect on a scalar object is unsequenced relative to a value computation using the value of the same scalar object, the behavior is undefined.

Is this not the case here? The post-inc-operator's side effect on i is unsequenced relative to the value computation of the addition operator, which uses the same i.

Why is this expression not usually said to be undefined?

Is it because the addition operator is thought to incur a function call for which stricter sequencing guarantees are given?

JMC
  • 1,723
  • 1
  • 11
  • 20
  • [Related](https://stackoverflow.com/q/4968854/10957435), though I guess not a duplicate since you've explained how it's different. Also [this](https://stackoverflow.com/q/24194076/10957435). I recon you could consider this background information for future readers. –  Nov 13 '19 at 03:32

4 Answers4

4

What ensures that the postfix's side-effect occurs after the computation of +?

There is no such assurance. The postfix's side effect may occur either before or after the value computation of + .

The post-inc-operator's side effect on i is unsequenced relative to the value computation of the addition operator, which uses the same i.

No, the value computation of the addition operator uses the result of value computation of its operands. The operands of + are i++ (not i), and 1. As you covered in the question, the read of i is sequenced-before the value computation of i++, and therefore (transitivity) sequenced before the value computation of +.

The following things are guaranteed to happen in the following order:

  1. Read of i.
  2. Value computation of ++ (operand: result-of-step-1)
  3. Value computation of + (operands result-of-step-2 and 1)

And the side-effect of i++ must occur after step 1 but it could be anywhere upto that constraint.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • "No, the value computation of the addition operator uses the result of value computation of its operands. The operands of + are i++ (not i), and 1" Oh, I understand it now, thanks. I got so lost in the wording I didn't realize anymore that the "value computation" is quite literally the process of determining the value to be used. – JMC Nov 13 '19 at 02:34
2

i++ + 1 is not undefined on account of the use of the postfix operator because it perpetrates only one side effect on one object, and that object's value is only referenced in that place. The i++ expression unambiguously produces the prior value of i, and that value is what is added to 1, no matter when i is actually updated.

(We don't know that i++ + 1 is well-defined, because things can go wrong for various other reasons: i being uninitialized or otherwise indeterminate or invalid, or numeric overflow or pointer overrun being perpetrated.)

Undefined behavior occurs if in the same evaluation phase we try to modify the same object twice: i++ + i++. This can be convoluted with pointers, because (*p)++ + (*q)++ increment the same object only if p and q point to the same location; otherwise it is fine.

Undefined behavior also occurs if in the same evaluation phase, we try to observe the value of an object that is modified elsewhere in the expression, like i++ + i. The right hand side of the + accesses i, but that is not sequenced with regard to the side effect of i++ on the left; the + operator doesn't impose a sequence point. In i++ + 1, the 1 doesn't try to access i, needless to say.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • This is how I also always thought about it, until I looked into the specific wording. I think that the important point is "value computation USING the value..." I now think this might be meant to mean other value computations OF i itself, as not value computations which involve the previously computed value of i, in which case this would not apply to the +-operator. – JMC Nov 13 '19 at 01:55
  • @JMC The wording has changed in the C standard over the years, and also C++ has its own wording. The basic ideas are the same, in the light of simple expressions like these. – Kaz Nov 13 '19 at 01:59
  • @JMC E.g. C90 had wording like *"... used only to determine the value to be stored"*. But then, it's possible to write expressions where an object is used to determine **where** a value is stored. Another monkey wrench is that data flow dependencies are logically sequencing: you can't store a value that has not yet been computed, and you can't compute a value whose operands have not been accessed. Purely functional languages take advantage of this in order to sequence I/O (like Haskell monads and whatnot). – Kaz Nov 13 '19 at 02:02
  • I know that this is usually how it works and how it is described in secondary literature, my question is rather, were exactly in the standard, or something close to the standard, is it stated that this is the case? By the pure "letter-of-the-law"-interpretation, I don't see why the side effect of ++ could not happen between the evaluation of i++ and the computation of the addition. Is it because I have a wrong understanding of what "value computation" means? – JMC Nov 13 '19 at 02:03
  • 1
    @JMC The `+` operator doesn't access the value of `i`; it uses the result of the `i++` expression! The `i++` expression accesses `i`, and that is allowed, if that `i++` is the only expression doing so. `i++` is the expression which controls the increment, so of course it can reliably access and yield the prior value of `i`. – Kaz Nov 13 '19 at 02:07
  • 1
    @JMC What is also well defined is, say, something pedestrian like `i = i + i + 1`. This conforms with the original C90 wording that `i` is accessed only to determine the value to be stored. The update of `i` by `i = ` requires the value to be stored to be calculated first, and both accesses to `i` on the other side participate in producing the inputs that go into calculating that value, so things are copacetic. – Kaz Nov 13 '19 at 02:12
  • This comment of yours made it clear to me, thanks you. I'm going to accept M.M.'s answer as it is an answer rather than a comment and expresses the same Idea in more detail. – JMC Nov 13 '19 at 02:35
  • @Kaz: Given `struct foo { struct foo *next; } *p1,*p2;`, I don't think the authors of the Standard particularly intended to forbid an implementation given something like `p1->next->next = p2;` from storing the lower half of `p2` to the lower half of `p1->next->p`, and then storing the upper half of `p2` to the upper half of `p2->next->p` (rereading `p2->next`) on platforms where that would be the most efficient way to perform the operation, even though that could malfunction badly if `p1->next` equals `p1`. On the other hand, they presumably expected that such split writes would... – supercat Nov 13 '19 at 17:13
  • ...only be a concern on platforms that would have some reason to reread `p1->next` between the two writes, and that people working with such platforms would be better placed than the Committee to judge the costs and benefits of guaranteeing behavior in such cases. – supercat Nov 13 '19 at 17:14
2

Here's what happens when i++ + 1 is evaluated:

  • The subexpression i++ is evaluated. It yields the previous value of i.
  • Evaluating i++ also has the side effect of incrementing the stored value of i -- but note that that incremented value is not used.
  • The subexpression 1 is evaluated, yielding the obvious value.
  • The + operator is evaluated, yielding the result of i++ plus the result of 1. This can happen only after the values of the left and right subexpressions are determined (but it can consistently happen before or after the side effect occurs).

The side effect of the ++ operator is only guaranteed to happen some time before the next sequence point. (That's in C99 terms. The C11 standard presents the same rules in a different way.) But since nothing else in the expression depends on that side effect, it doesn't matter when it occurs. There is no conflict, so there's no undefined behavior.

In i++ + i, the evaluation of i on the RHS will yield different results depending on whether the side effect has happened yet or not. And since the ordering is undefined, the standard throws up its hands and says the behavior is undefined. But in i++ + i, that problem doesn't occur.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
1

"What ensures that the postfix's side-effect occurs after the computation of +?"

Nothing makes that specific guarantee. You must act as if you're using the original value of i, and at some point it needs to perform the side-effect, but as long as everything behaves properly, it doesn't matter how the compiler implements this or in what order. It can (and for certain scenarios, would) implement it as roughly equivalent to either:

auto tmp = i;
i = tmp + 1; // Could be done here, or after the next expression, doesn't matter since i isn't read again 
tmp + 1;  // produces actual value of i++ + 1

or

auto tmp = i + 1;
i = tmp; // Could be done here, or after the next expression, doesn't matter since tmp isn't changed again
(tmp - 1) + 1; // produces actual value of i++ + 1

or (for primitives or inlined operator overloads where it has enough information) optimize the expression to just:

++i; // Usually the same as i++ + 1 if compiler has enough knowledge

because postfix increment followed by adding one could be treated as prefix increment without adding one after.

Point is, it's up to the compiler to ensure the side-effect occurs sometime, which might be before or after the computation of +; the compiler just needs to make sure it has stored, or can recover, the original value of i.

The various contortions here might seem pointless (clearly ++i is the best if you can swing it, and i + 1; followed by ++i is simplest otherwise), but they're often necessary to work with the hardware atomics on a given architecture; if the architecture offers a fetch_then_add instruction, you'd want to implement it as:

 auto tmp = fetch_then_add(i, 1); // Returns original value of i, while atomically adding 1
 tmp + 1;

but if it only offers an add_then_fetch instruction, you'd want:

auto tmp = add_then_fetch(i, 1); // Returns incremented value of i
(tmp - 1) + 1;

As with many things, the C++ standard doesn't impose a preferred order because real hardware doesn't always cooperate; if it gets the job done and behaves as documented, it doesn't really matter what order it used.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271