views:

152

answers:

3

This is a stupid question. :)

[EDIT: stupid or not, this turned out to be a C++ peculiarity question, see UPDATE_2]

Suppose we have:

int a = 0; // line 1
int b = ++a; // line 2

What happens in line 2 is (note, numbers are just markers and do not specify exact order):

                      = [1: write result of (3) to result of (2)]
                     /\
[2: take "b" l-value] [3: convert result of (4) to an r-value ]
                      |
                      [4: take "a" l-value, "increment" and return it]

The "write" in (4) is "ordered" before the "read" in (3), and since there are no sequence points between, the side-effect is not guaranteed to happen before (3) (there is also a "read" inside (4) itself, but ordered before "write", so that does not yield UB).

So, where is the error in the above?

[UPDATE, aimed at not seasoned sequence-point lawyers :)]

In other words the problem is:

  1. There seems to be a "competition" whether the l-value-to-r-value conversion ("read") or increment ("write") side-effect takes place first.

  2. In C, that would give an UB, according to JTC1/SC22/WG14 N926 "Sequence Point Analysis"* (see, for example, EXAMPLE 5: int x,y; (x=y) + x; // UB).

  3. Note this would not be a case should postincrement be used since (3) and (4) will constitute a single [(3): take "a" l-value, convert it to r-value and return that r-value] with the "write" side-effect delayed until somewhere before the next sequence point

_

(*) This looks like the cleanest systematic rationale for the topic given by the C99 Standards Committee members.

[UPDATE_2]

  1. Lesson learned: never judge C++ by C rules :)). I did exactly that wondering why the N926 (which cleanly describes the C99 way of things) was "not clear enough" on the topic of preincrements yielding l-values.

  2. The question arises, how would one build a similar rationale for C++, as there isn't one, since even in the C case just interpreting the standard is pretty hard, and the C++ language standard is much more complicated and obscure.

+1  A: 

I think the solution may be in the wording of "++i". It says "The value is the new value of the operand; it is an lvalue.". And behavior is undefined in 5/4 by "Furthermore, the prior value shall be accessed only to determine the value to be stored.".

So, we are not accessing the prior, but the new value. And then we may be fine. It seems to be a very thin line between undefined behavior and defined behavior though.

Actually "prior value" sounds to me like "value the object had at the previous sequence point". And if interpreted like that, then this construct looks undefined. But if we directly compare the wording of "++i" in 5.3/2 to 5/4, we are confronted by "new value" vs "prior value", and things are "bent" to defined behavior ("++i" would peek at the value of "i" at the next sequence point, and produce that value as the contents of the resulting lvalue of "++i").

Johannes Schaub - litb
So, do you see a way to extend N926 analysis method to support the C++ case in question?
mlvljr
+1  A: 

The main sentence in C++ 5/4 is

Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression.

(And the sentence quoted by Johannes is the following, subordinate one.)

Which scalar object do you suspect is having its stored value modified more than once here? a and b are modified once each on line 2, so there is no problem.

(Similar language is in 6.5/2 in the C standard.)


EDIT:

The "write" in (4) is "ordered" before the "read" in (3), and since there are no sequence points between, the side-effect is not guaranteed to happen before (3)

On re-reading your question, I think the confusion comes from a confused way of thinking about ++a: the expression doesn't really peek at the future value of a. Rather, it might help to think of ++a as "return a+1 and as a side-effect increment a, at your leisure (before the next sequence point)".

So who cares whether that side-effect happens before or after (3)? The value of the expression, which is what is fed to (3), is already determined.

John Marshall
@John, the point is that when you do `++a` in C++, it does *not* automatically yield `a+1` as a value independent of changing its stored value, because then it can't be an lvalue. In C++ you can do such things as `int *p = ` which are not allowed in C. What happens in C++ for stuff like `++a + 1` is that you modify the stored value of `a` and also read its value inbetween the same sequence points *not* to determine the value to be stored but to further process it.
Johannes Schaub - litb
@litb: exactly, I'm updating my post with what actually was a comment outpaced by yours:))
mlvljr
Chapter and verse? Because I keep reading 5/4 and 5.3.2/2 ("The value is the new value of the operand; it is an lvalue") and I just don't see that that implies that the scalar object is re-read (in an as-if world).
John Marshall
@John, well the wording is unfortunate (since it uses "value", which in C terms means "non-lvalue", but in C++ doesn't imply that). It is fixed in C++0x to say "The result is the updated operand.". But as it is an lvalue, there is no doubt about a second read. See 4.1 for the actions taken to convert an lvalue to an rvalue, which includes accessing the value stored therein. And 5/8 which is responsible for triggerig that conversion if an operand is an lvalue but an rvalue is expected.
Johannes Schaub - litb
+1  A: 

Standard states:

int b = ++a;

"If x is not of type bool, the expression ++x is equivalent to x+=1."

int b = a+=1;

"The behavior of an expression of the form E1 op= E2 is equivalent to E1 = E1 op E2 except that E1 is evaluated only once. In += and =, E1 shall either have arithmetic type or be a pointer to a possibly cv qualified completely defined object type. In all other cases, E1 shall have arithmetic type."

int b = a = a + 1;

"In simple assignment (=), the value of the expression replaces that of the object referred to by the left operand. "

Most important:

"Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored."

At the end, the expression "a = a + 1;" is evaluated to 1 which yields (well defined behavior):

int b = 1;

sinec