What is a sequence point?

D

Deniz Bahar

I know the basic definition of a sequence point (point where all side
effects guaranteed to be finished), but I am confused about this
statement:

"Between the previous and next sequence point an 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."

Can someone give me examples of expressions that "barely" break this
rule (the type a newcomer might trip over)?

If you are between sequence points, then what does that second sentence
mean "prior value"?
 
R

Randy Howard

I know the basic definition of a sequence point (point where all side
effects guaranteed to be finished), but I am confused about this
statement:

"Between the previous and next sequence point an 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."

Can someone give me examples of expressions that "barely" break this
rule (the type a newcomer might trip over)?

If you are between sequence points, then what does that second sentence
mean "prior value"?

Specifically,
http://www.eskimo.com/~scs/C-faq/q3.8.html

Also related:
http://www.eskimo.com/~scs/C-faq/q3.1.html
http://www.eskimo.com/~scs/C-faq/q3.2.html
http://www.eskimo.com/~scs/C-faq/q3.3.html
http://www.eskimo.com/~scs/C-faq/q3.9.html

Then, read the entire FAQ.

Then read all of it again. :)
 
D

Deniz Bahar

Randy said:

I read the FAQ before my original post. It was the FAQ that actually
tickled my mind and made me wonder if I was just lucky all this time
when I was using side effects in expressions.

This sentence from the FAQ really confused me:

"The second sentence can be difficult to understand. It says that if an
object is written to within a full expression, any and all accesses to
it within the same expression must be for the purposes of computing the
value to be written. This rule effectively constrains legal expressions
to those in which the accesses demonstrably precede the modification."

More confused about the FAQ's explanation of the sentence from the
standard than the standard itself. I'm all around confused about side
effect and sequence points :_(
 
R

Randy Howard

I read the FAQ before my original post. It was the FAQ that actually
tickled my mind and made me wonder if I was just lucky all this time
when I was using side effects in expressions.

Ok, well, the links after the first one contain specific examples. I
was hoping that one or more of them would turn the lightbulb on for
you because I usually find that examples can be helpful when the legalese
gets overwhelming.

In particular, 3.2 has a more lengthy explanation, and a common example
as well. It seems to explain it pretty well to me, so if that doesn't do
it for you, maybe somebody else can provide a better explanation.
 
P

pete

Deniz said:
I know the basic definition of a sequence point (point where all side
effects guaranteed to be finished), but I am confused about this
statement:

"Between the previous and next sequence point an 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."

Can someone give me examples of expressions that "barely" break this
rule (the type a newcomer might trip over)?

If you are between sequence points,
then what does that second sentence
mean "prior value"?

(p = p -> next = q)

The above doesn't specify the order of assignment and is undefined.
It could be either (p = q, p -> next = q) or (p -> next = q, p = q).

(i = x++)
The above is being debated on comp.std.c
The i in x++ is evaluated only once, but for two reasons.
It's evaluated to determine the value of the right operand of
the assignment operator, but also to determine the address of
the lvalue of the increment operator.
 
R

Richard Bos

Deniz Bahar said:
I know the basic definition of a sequence point (point where all side
effects guaranteed to be finished), but I am confused about this
statement:

"Between the previous and next sequence point an 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."

Can someone give me examples of expressions that "barely" break this
rule (the type a newcomer might trip over)?

If you are between sequence points, then what does that second sentence
mean "prior value"?

A sequence point is a point in the execution of a program at which all
side effects (that is, all assignments, file output and volatile
accesses) must have been completed. Examples are at the end of a
complete expression, just before a function call, and at the logical,
comma and conditional operators.
The compiler is allowed to "save up" or mix side effects between two
adjacent sequence points, for example if that results in more efficient
code. However, _at_ a sequence point, all side effects must be complete.


This is useful; it allows both

int a,b;
char x[10],y[10];

...
x[a++]=y[b++];

to be compiled efficiently (possibly with both increments being done in
parallel), _and_

if (valid_index(a) && array[a]>cutoff)

to be compiled safely, that is, without the compiler trying to be
efficient by computing array[a] before we're sure a is valid.

It also means (and here we come to your example) that

a=i++;

is invalid code, because i++ is both incremented, _and_ read for another
purpose than the increment. In the sub-expression i++, i is given a new
value; but in the sub-expression a, the _prior value_ of i, that is,
its value before the increment, is used to determine which array member
to assign to.

Richard
 
L

Luke Wu

Deniz said:
I know the basic definition of a sequence point (point where all side
effects guaranteed to be finished), but I am confused about this
statement:

"Between the previous and next sequence point an 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."


I don't know where you got that passage from, but this one from the
last C89 draft is a lot more clear:

"[If] an object is modified more than once, or is modified and accessed
other than to determine the new value, between two sequence points [,
undefined behaviour reusults]"

There are two parts to this:

1) An object can't be modified more than once between sequence points:
examples

j = i-- * i--; /* more obvious fault */

j = ++i * ++i; /* less obvious, but still breaches rule */

You might think that the pre-increments are okay because side effects
happen immediately, but it doesn't matter because the literature says
you can't modify an object more than once.


2) Between sequence points, an object can't be modified and accessed
other than to determine it's new value (trickier of the two parts of
the rule).

examples

i = i + 3; this is okay because i is modified (i = ____) and accessed
(rvalue the the left of =) only to determine the new value for the i

(i += 3) * j; this is not okay, because i is modified (i += _-__) and
accessed (rvalue of i later used to multiply with j) for a reason that
isn't to determine the new value of i.

i = (i += 3) * j; this is okay, because i is modified (i += ____) and
is accessed to determine the new value of i (left side i = _____).

a[i++]; this is not okay, because i is modified [i++] then accessed to
determine something other than the new value of i.

I hope this helps.
 
L

Luke Wu

pete said:
(p = p -> next = q)

The above doesn't specify the order of assignment and is undefined.
It could be either (p = q, p -> next = q) or (p -> next = q, p = q).

but =assignment operators associate left to right, so it would be:

(p -> next = q, p = q)
(i = x++)
The above is being debated on comp.std.c
The i in x++ is evaluated only once, but for two reasons.
It's evaluated to determine the value of the right operand of
the assignment operator, but also to determine the address of
the lvalue of the increment operator.

this would seem to break the rule, because the postincrement is not an
access to determine the new value of i
 
I

infobahn

Luke said:
but =assignment operators associate left to right, so it would be:

(p -> next = q, p = q)

Wrong. Because there's no sequence point, the compiler is free to do
the evaluations in any order it likes. Associativity does not
determine the order of evaluation.
 
L

Lawrence Kirby

I don't know where you got that passage from, but this one from the
last C89 draft is a lot more clear:

The text is that in the current standard.
"[If] an object is modified more than once, or is modified and accessed
other than to determine the new value, between two sequence points [,
undefined behaviour reusults]"

There are two parts to this:

1) An object can't be modified more than once between sequence points:
examples

j = i-- * i--; /* more obvious fault */

j = ++i * ++i; /* less obvious, but still breaches rule */

You might think that the pre-increments are okay because side effects
happen immediately, but it doesn't matter because the literature says
you can't modify an object more than once.


2) Between sequence points, an object can't be modified and accessed
other than to determine it's new value (trickier of the two parts of
the rule).

examples

i = i + 3; this is okay because i is modified (i = ____) and accessed
(rvalue the the left of =) only to determine the new value for the i

(i += 3) * j; this is not okay, because i is modified (i += _-__) and
accessed (rvalue of i later used to multiply with j) for a reason that
isn't to determine the new value of i.

This is fine. By your logic you couldn't use the result of an assignment
operator for anything. Perhaps this is one reason why the draft text
you cite was corrected?
i = (i += 3) * j; this is okay, because i is modified (i += ____) and
is accessed to determine the new value of i (left side i = _____).

i is modified twice between sequence points so this is undefined.
a[i++]; this is not okay, because i is modified [i++] then accessed to
determine something other than the new value of i.

Again, this is perfectly fine.

Note that neither assignment nor increment/decrement operators reread the
value of the object after it has been modified.

Lawrence
 
L

Luke Wu

meant to say right to left
Wrong. Because there's no sequence point, the compiler is free to do
the evaluations in any order it likes. Associativity does not
determine the order of evaluation.

Yes, but = is a binary operator, and works on 2 operands.

So you can either have p = p->next, p->next =q OR p->next = q, p =
p->next

How can you have p = q ?? never is a single = operating on those two
operands
 
G

G Fernandes

Deniz said:
I know the basic definition of a sequence point (point where all side
effects guaranteed to be finished), but I am confused about this
statement:

"Between the previous and next sequence point an 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."

Can someone give me examples of expressions that "barely" break this
rule (the type a newcomer might trip over)?

If you are between sequence points, then what does that second sentence
mean "prior value"?

Dont forget about function calls (the commas in them are not sequence
points).
A call such as this:

f(n++, n);

has undefined behavior because it violates the rule you quoted cited.
There's no sequence point between the argument expressions. The first
(left) argument modifies n. It also reads the value of n, but only to
determine the new value to be stored in n. So far, so good. However,
the second (right) argument expression reads the value of n between the
same pair of sequence points as the first argument, but not to
determine the value to be stored in n. This additional attempt to read
the value of n has undefined behavior.
 
I

infobahn

Luke said:
Yes, but = is a binary operator, and works on 2 operands.

So what? The compiler can view it this way:

"We have an expression of the form a = b = c; fine, I'll make them
all equal, so we'll set a equal to c and we'll set b equal to c",
so it could set p equal to q and then set p->next equal to q, with
the result that q->next = q;

or this way: it could set p->next to be equal to q, and then set p
to be equal to p->next, which would not affect q->next at all.
Either behaviour (and any other behaviour!) is legitimate.
 
L

Luke Wu

infobahn said:
So what? The compiler can view it this way:

"We have an expression of the form a = b = c; fine, I'll make them
all equal, so we'll set a equal to c and we'll set b equal to c",
so it could set p equal to q and then set p->next equal to q, with
the result that q->next = q;

No, the compiler views it according to the associativity rules(right to
left) as this:

(a = (b = c)) <=== right to left associativity

Consider also (all ints):

a + b + c

the value in a is equal to -b, the value in both b and c is almost
INT_MAX, so will over flow occur?

Based on your argument, overflow 'ca'n occur, because the compiler is
free to do (b + c) before adding the result to a.

In reality, overflow never occurs because we know the compiler will
consider that expression as:

((a + b) + c) <=== left to right associativity

the result will be rvalue(c), never an overflow in an intermediate step
 
L

Luke Wu

G said:
Dont forget about function calls (the commas in them are not sequence
points).
A call such as this:

f(n++, n);

has undefined behavior because it violates the rule you quoted cited.
There's no sequence point between the argument expressions. The first
(left) argument modifies n. It also reads the value of n, but only to
determine the new value to be stored in n. So far, so good. However,
the second (right) argument expression reads the value of n between the
same pair of sequence points as the first argument, but not to
determine the value to be stored in n. This additional attempt to read
the value of n has undefined behavior.

"It also reads the value of n, but only to determine the new value to
be stored in n." No it doesn't, the first argument reads the value of n
to be sent to the function, not to be stored in n.
 
M

Mark McIntyre

infobahn wrote:
No, the compiler views it according to the associativity rules(right to
left) as this:

(a = (b = c)) <=== right to left associativity

The standard discusses this type of problem. In 5.1.2.3, it shows examples
and notes that in general you cannot assume the normal mathematical laws of
associativity to be applicable.
 
M

Mark McIntyre

"It also reads the value of n, but only to determine the new value to
be stored in n." No it doesn't, the first argument reads the value of n
to be sent to the function, not to be stored in n.

It does both.

For the first argument n is evaluated, and stored for passing to f. n is
also incremented, and stored back into n.

The second argument evaluates n and stores it for passing to f.

The order in which the two arguments are evaluated is undefined. The timing
of the increment-and-store-back is undefined. Its possible for the
increment to happen before or after the 2nd argument is evaluated. Or
simultaneously.
 
D

Dave Vandervies

Luke Wu said:
No, the compiler views it according to the associativity rules(right to
left) as this:

(a = (b = c)) <=== right to left associativity

Yes, that's how it's parsed.
No, that doesn't impose requirements on the order things are actually
done. All that's required is that these happen:
- c is evaluated to get a value
- The value of c, converted appropriately, is stored in b
- The value stored in b, converted appropriately, is stored in a.
Which order they happen in depends only on dependencies between them,
and the store to a only depends on the value stored in b being calculated
(and even there the compiler may be able to determine what it is without
actually calculating it - see my example below), not on it actually
being stored in b.

If they all have the same types, any self-respecting optimizing compiler
will load the value once and store it twice without doing anything else
between the stores. In the simple case it will assign to b first, but
there's no reason why it needs to, and (especially if they're not both
local variables) there might be good reasons not to.


If different types (and, therefore, conversions) are involved, it's even
easier to come up with a perfectly reasonable way for the store to a to
happen first, or for both stores to happen at the same time.
Consider, f'rexample, a word-addressed machine where char * and void *
(as "byte pointers") have a different representation than other pointers.
(Such machines do exist, and as far as I know have C implementations;
Chris Torek likes using one of them as an example.)
If a and c are, say, int * and b is a void *, then the most sensible
way to execute "a=b=c" is:
--------
load r1,c ;Load value to be stored
load a1,a ;Load location to store word pointer
store r1,a1 ;Store word pointer
shl r1,2 ;Convert word pointer to byte pointer
load a1,b ;Load location to store byte pointer
store r1,a1 ;Store byte pointer
--------
Since the word pointer -> byte pointer -> word pointer conversion ends
up being a no-op, this reordering gives the exact same results as if
the double conversion had been done, and is therefore just as valid.

For the case where they're all the same type, consider
Consider also (all ints):

a + b + c

the value in a is equal to -b, the value in both b and c is almost
INT_MAX, so will over flow occur?

If the value of a+b can be added to c without overflowing, the generated
code has to act as if no overflow occurred, since the value being added
to c is the value of a+b.
If the compiler knows that, f'rexample, the machine uses 2s-complement
with silent wraparound on overflow, it's free to generate code that
computes b+c and then adds a to that, since it knows that the result
will be the same whether something overflows or not.

The only requirement is that the correct values get stored in the
correct places before the next sequence point. Operations without
dependencies, or operations that can commute with each other, can be
(and, with optimizing compilers, usually are) done in any order the
compiler likes, no matter what the code says.


dave
 
L

Luke Wu

Dave said:
Yes, that's how it's parsed.
No, that doesn't impose requirements on the order things are actually
done. All that's required is that these happen:
- c is evaluated to get a value
- The value of c, converted appropriately, is stored in b
- The value stored in b, converted appropriately, is stored in a.
Which order they happen in depends only on dependencies between them,
and the store to a only depends on the value stored in b being calculated
(and even there the compiler may be able to determine what it is without
actually calculating it - see my example below), not on it actually
being stored in b.

The compiler can do anything it wants at the machine level, but the
result must not differ from what we would get if we did b=c , a=b at
the abstract machine level

if in (a = (b = c)) , the order really does matter (if a, b, c are sub
expressions), then the order implied by the brackets must be taken, if
order doesn't matter, the compiler can do anything

the result of anything the compiler does, must equal the result of:
b=c,a=b

I am absolutely certain of this.
 
J

Jack Klein

The compiler can do anything it wants at the machine level, but the
result must not differ from what we would get if we did b=c , a=b at
the abstract machine level

if in (a = (b = c)) , the order really does matter (if a, b, c are sub
expressions), then the order implied by the brackets must be taken, if
order doesn't matter, the compiler can do anything

the result of anything the compiler does, must equal the result of:
b=c,a=b

I am absolutely certain of this.

And I am absolutely certain that you are wrong. The difference is
that I can cite the reference to the C language standard to back up my
certainty, and you cannot.

The C standard specifically states, in paragraph 2 of section 6.5:

========
Between the previous and next sequence point an object shall have its
stored value modified at most once by the evaluation of an expression.
Furthermore, the prior value shall be read only to determine the value
to be stored.70)
========

That "70" at the end is a reference to a footnote, subscripts don't
look special in plain text. But here is the context of footnote 70:

========
70) This paragraph renders undefined statement expressions such as
i = ++i + 1;
a[i++] = i;
while allowing
i = i + 1;
a = i;
========

So you may be certain, but you are certainly wrong.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top