Discussion: Re: sequence points
Afficher un message
Vieux 16/10/2007, 03h49   #6
Kai-Uwe Bux
Aucun Avatar
 
Messages: n/a
Hébergeur:
Par défaut Re: sequence points

James Kanze wrote:

> On Oct 15, 2:43 am, Lance Diduck <lancedid...@nyc.rr.com> wrote:
>> On Oct 14, 4:47 pm, Kai-Uwe Bux <jkherci...@gmx.net> wrote:
>> > Please consider

>
>> > #include <iostream>

>
>> > int main ( void ) {
>> > int a = 0;
>> > a = ( (a = 5), 4 ); // (*)
>> > std::cout << a << '\n';
>> > }

>
>> > I would like to determine whether line(*) has undefined
>> > behavior (modifying a variable twice between sequence
>> > points).

>
>> > What I think is this: We have an assignment expression

>
>> > lhs = rhs

>
>> > where the right hand side is this:

>
>> > (a = 5), 4

>
>> > I note that the comma will introduce a sequence point that
>> > separates the side-effects of a=5 from the side-effects of
>> > 4. The main question therefore is whether the right hand
>> > side

>
>> > (a = 5), 4

>
>> > can have a value before the side effects of its evaluation
>> > take place. My understanding of the standard is that
>> > sequence points only separate side effect but do not tell us
>> > anything about how, where and when values of expressions are
>> > established. However, if that understanding is correct, the
>> > abstract machine would be allowed to predict that the right
>> > hand side will have value 4 and perform the side effect of
>> > the ambient assignment

>
>> > lhs = rhs

>
>> > before the side effects of the rhs take place. Along this
>> > possible path of execution, the value of a would be modified
>> > twice. Thus, as of now, I believe that line (*) has
>> > undefined behavior.

>
>> > However, I am not sure about this interpretation of the
>> > standard; and I would appreciate your . (In fact, I hope
>> > that I am wrong.)

>
>> > Note that similar considerations would apply to:

>
>> > a = f();

>
>> > Is it guaranteed that the side-effects of evaluating f()
>> > take place before the value of a is changed? or would a
>> > conforming implementation allowed to put all side-effects of
>> > f() on hold until it knows the value, perform the
>> > assignment, and then run the side-effects of f()?

>
>> I would believe that an good optimising compiler would recognize that
>> int a=0;
>> a=((a=5),4);
>> cout<<a;
>> would reduce to
>> cout<<4;
>> since assigning (non volatile)int has no side effects. Whether
>> this is correct by the standard (the C standard at that) there
>> is probably no consensus.

>
> That's definitly allowed, under the "as if" rule, even if the
> expression in question has no undefined behavior, but that's a
> separate issue.
>
> The issue with sequence points is more complex. As Kai-Uwe
> correctly understands, they only introduce a partial ordering.
> I *think* that in this case, the two assignments are ordered,
> because the outer assignment requires the results of the rhs,
> and there is a sequence point in the expression there.

[snip]

That actually is the core of the matter.

Let me slightly modify the example so that all subexpressions have
side-effects:

int a = 0;
int b = 0;
a = ( ( a = 5 ), ( b = 4 ) );

The comma operator introduces a sequence point that clearly separates

a=5 from b=4

The question at hand is whether this sequence point also separates

a = rhs from a=5


On a more fundamental level, it is the very computational model of C++ about
which I am not sure. It seems to me that there is an 'obvious'
understanding: an expression specifies a computation, the computation has
side-effect and yields a value. The value is not available to take part in
other side-effect before it has been computed.

However, there is also a 'counter-intuitive' reading of the standard: an
expression specifies a computation. This computation establishes a value
and can cause side-effect. However, the time at which side-effect takes
place is independent of the actual establishment of the value. In
particular, a conforming implementation could proceed as follows: For each
expression, create (bottom-up starting with innermost subexpressions) a
pair: (value, instruction_sequence) where the value is the value and the
instruction_sequence can be executed to make all side-effects of the
evaluation happen. Then, for each operator, we have a rule like these

given lhs + rhs:
find the pairs (lhs_value, lhs_sequence) and (rhs_value, rhs_sequence)
the pair for lhs+rhs is:
(lhs_value + rhs_value, shuffle( lhs_sequence, rhs_sequence) )

given lhs = rhs
find ( lhs_value, lhs_sesquence ) and ( rhs_value, rhs_sequence )
the pair for lhs = rhs is:
( rhs_value, shuffle( lhs_sequence, rhs_sequence, {assignment} ) )

given lhs, rhs
the pair for lhs, rhs is:
( rhs_value, concat( lhs_sequence, rhs_sequence ) )
^^^^^^

Note how the last rule will ensure that all side-effects of the left-hand
side in a comma-expression take place before any side-effect of the
right-hand side.

If such an implementation was conforming, the comma sequence point would not
separate the outer assignment from the inner.


> Note that in C++, the current draft replaces sequence points
> with another concept, in order to be able to define ordering
> when threads are involved, so the answer may change in the next
> version of the standard.


Good point. It appears that the next standard is much clearer with regard to
the example above.

The draft that I have uses "sequenced before" and "sequenced after" to
define a partial order on computations and side-effects. It says about
assignment

In all cases, the assignment is sequenced after the value computation of
the right and left operands, and before the value computation of the
assignment expression.

This seems to say that

int volatile a = 0;
int volatile b = 4;
a = ( b = 3 );

sequences the assignment to b before the value of b=3 is computed, which in
turn is sequenced before the assignment to a.

It also says that in

a = ( ( a = 5 ), ( b = 4 ) )

the outer assignment is sequenced after the value computation of the rhs. It
does not say that the outer assignment is sequenced after the side-effects
of the right hand side.

However(!), the draft says about the comma operator:

Every value computation and side effect associated with the left
expression is sequenced before every value computation and side effect
associated with the right expression.

If we take it that the value-computation of the rhs in a comma-expression
is involved in the the value-computation of the comma-expression, then this
says that at least the side-effects of a = 5 are sequenced before the
value-computation of b = 4, which in turn is sequenced before the outer
assignment.


The main difference between the draft and the standard with regard to this
example seems to be that, in the draft, sequencing restrictions are given
for value-computations and side-effects, whereas in the current standard,
sequencing restrictions are only given for side-effects.


[snip]


Best

Kai-Uwe Bux
  Réponse avec citation
 
Page generated in 0,09876 seconds with 9 queries