Volatiles in assignment expressions

D

Daniel W

Hi!

I tried to post this to comp.lang.c.moderated but it didn't
seem to go through.

I've got a question about volatiles in assignment expressions.
I found the following code snippet in an embedded development
forum:

----
volatile char a;

...

if (++a == 1)
{
...
----

This code snippet was then followed by the output from two
compilers, one which was gcc. Gcc's code looked like something
like this (in RTL like form where Rn denotes a register):

R1 <- *a
R1 <- R1 + 1
*a <- R1
R2 <- *a
IF R2 == 1

Now to my question! The code above contains two read accesses
and one write access to a. I would have assumed that only one
read access should be performed. Does the standard allow the
second read operation?

I am adding a lot of standard texts that I've used when trying
to deduce the answer. Please help if you can!

Sorry for the long posting!

Regards
/Daniel W

P.s. Remove underscores from e-mail address if replying via
e-mail!

------------------------------------------------------------
Reading from the standard ISO/IEC [9899:1999] I find the
following passages related to this issue (A denotes the
rationale and B denotes the standard):

B.5.1.2.3 - 2 "Accessing a volatile object, modifying an object,
modifying a file, or calling a function that does any of those
operations are all side effects, which are changes in the state
of the execution environment. Evaluation of an expression may
produce side effects. At certain specified points in the execution
sequence called sequence points, all side effects of previous
evaluations shall be complete and no side effects of subsequent
evaluations shall have taken place." ...

B.6.5.16 - 3 "An assignment operator stores a value in the
object designated by the left operand. An assignment expression
has the value of the left operand after the assignment, but is
not an lvalue. The type of an assignment expression is the type
of the left operand unless the left operand has qualified type,
in which case it is the unqualified version of the type of the
left operand. The side effect of updating the stored value of
the left operand shall occur between the previous and the next
sequence point."

B.6.5.16.2 - 3 "A compound assignment of the form E1 op= E2
differs from the simple assignment expression E1 = E1 op E2 only
in that the lvalue E1 is evaluated only once."

B.6.3.2.1 - 2 "Except when it is the operand of the sizeof operator,
the unary & operator, the ++ operator, the -- operator, or the left
operand of the . operator or an assignment operator, an lvalue that
does not have array type is converted to the value stored in the
designated object (and is no longer an lvalue). If the lvalue have
qualified type, the value has the unqualified version of the type
of the lvalue; otherwise, the value has the type of the lvalue. If
the lvalue has an incomplete type and does not have array type, the
behavior is undefined."

B.6.5.3.1 - 2 "The value of the operand of the prefix ++ operator
is incremented. The result is the new value of the operand after
incrementation. The expression ++E is equivalent to (E+=1). See
discussions of additive operators and compound assignment for
information on constraints, types, side effects, and conversions
and the effects of operations on pointers."

B.6.7.3 - 3 "The properties associated with qualified types are
meaningfull only for expressions that are lvalues."

B.6.7.3 - 6 "An object that has volatile-qualified type may be
modified in ways unknown to the implementation or have other
unknown side effects. Therefore any expression refering to such
an object shall be evaulated strictly according to the rules of
the abstract machine, as described in 5.1.2.3. Furthermore, at
every sequence point the value stored in the object shall agree
with that prescribed by the abstract machine, except as modified
by the unknown factors mentioned previously. What constitutes
an access to an object that has volatile-qualified type is
implementetion-defined."

Annex C in the standard places a sequnce point after the controlling
expression of a selection statement.

A.6.5.2.4 "The C89 Committee did not endorse the practice in some
implementations of considering post-increment and post-decrement
operator expressions to be lvalues."

A.6.5.3.1 "See 6.5.2.4."

A.6.5.16 "The optimization rules for factoring out assignments can
also be stated. Let X(i,S) be an expression which contains no
impure functions or sequenced operators, and suppose that X
contains a storage S(i) to i which sets i to Snew(i) and returns
Sval(i). The possible expressions are

S(i): Sval(i): Snew(i):
++i i+1 i+1
i++ i i+1
--i i-1 i-1
i-- i i-1
y y y
i op= y i op y i op y

Then X(i,S) can be replaced by either

(T = i, i = Snew(i), X(T,Sval))

or

(T = X(i,Sval), i = Snew(i), T)

provided that neither i nor y have side effects themselves."
 
K

Keith Thompson

Daniel W said:
I tried to post this to comp.lang.c.moderated but it didn't
seem to go through.

Articles appear on comp.lang.c.moderated when the moderator gets
around to posting them. Yours will probably show up eventually (long
after your question has been answered to death here).
 
E

Eric Sosman

Daniel W wrote On 03/07/06 10:17,:
I've got a question about volatiles in assignment expressions.
I found the following code snippet in an embedded development
forum:

----
volatile char a;

...

if (++a == 1)
{
...
----

This code snippet was then followed by the output from two
compilers, one which was gcc. Gcc's code looked like something
like this (in RTL like form where Rn denotes a register):

R1 <- *a
R1 <- R1 + 1
*a <- R1
R2 <- *a
IF R2 == 1

Now to my question! The code above contains two read accesses
and one write access to a. I would have assumed that only one
read access should be performed. Does the standard allow the
second read operation?
[...]

Reading from the standard ISO/IEC [9899:1999] I find the
following passages related to this issue (A denotes the
rationale and B denotes the standard):
[...]
B.6.5.16 - 3 "An assignment operator stores a value in the
object designated by the left operand. An assignment expression
has the value of the left operand after the assignment, but is
not an lvalue. [...]

This seems to be where the ambiguity creeps in: must
"the value ... after the assignment" be read from the
volatile l.h.s. (seems to be gcc's interpretation), or
will the value assigned to the l.h.s. suffice (yours)?

The gcc reading seems defensible: "after" means "after,"
after all. Maybe the variable is really a special hardware
gadget that's connected up to an I/O device: You write a
command code to it, and when you read from it you get back
not what was written, but some kind of device status. That
is, the value as read might not resemble the value written.
 
J

Jordan Abel

Hi!

I tried to post this to comp.lang.c.moderated but it didn't
seem to go through.

I've got a question about volatiles in assignment expressions.
I found the following code snippet in an embedded development
forum:

----
volatile char a;

...

if (++a == 1)
{
...
----

This code snippet was then followed by the output from two
compilers, one which was gcc. Gcc's code looked like something
like this (in RTL like form where Rn denotes a register):

R1 <- *a
R1 <- R1 + 1
*a <- R1
R2 <- *a
IF R2 == 1

Now to my question! The code above contains two read accesses
and one write access to a.

That's because for some reason gcc chooses to read a again. However, the
C code only includes ONE read access - to read the original value which
is incremented. R2 <- R1 would probably have been more in the spirit of
the meaning of the original expression, since the prefix ++ operator
yields a specific value, not an alias for its argument. However, since
it results in the same behavior it is permitted.

Besides, the RTL code given is five statements, each presumably with its
own rtl analogue of a "sequence point", not one.
I would have assumed that only one read access should be performed.
Does the standard allow the second read operation?

The standard does not govern gcc's "RTL" language. There is only one
read access in the C code shown.
 
R

Robin Haigh

Eric Sosman said:
Daniel W wrote On 03/07/06 10:17,:
I've got a question about volatiles in assignment expressions.
I found the following code snippet in an embedded development
forum:

----
volatile char a;

...

if (++a == 1)
{
...
----

This code snippet was then followed by the output from two
compilers, one which was gcc. Gcc's code looked like something
like this (in RTL like form where Rn denotes a register):

R1 <- *a
R1 <- R1 + 1
*a <- R1
R2 <- *a
IF R2 == 1

Now to my question! The code above contains two read accesses
and one write access to a. I would have assumed that only one
read access should be performed. Does the standard allow the
second read operation?
[...]

Reading from the standard ISO/IEC [9899:1999] I find the
following passages related to this issue (A denotes the
rationale and B denotes the standard):
[...]
B.6.5.16 - 3 "An assignment operator stores a value in the
object designated by the left operand. An assignment expression
has the value of the left operand after the assignment, but is
not an lvalue. [...]

This seems to be where the ambiguity creeps in: must
"the value ... after the assignment" be read from the
volatile l.h.s. (seems to be gcc's interpretation), or
will the value assigned to the l.h.s. suffice (yours)?

The gcc reading seems defensible: "after" means "after,"
after all. Maybe the variable is really a special hardware
gadget that's connected up to an I/O device: You write a
command code to it, and when you read from it you get back
not what was written, but some kind of device status. That
is, the value as read might not resemble the value written.

The ambiguity here, if there is one, becomes more obvious if we consider f =
(g = h), with g volatile. Should the value assigned to f be that of h
(converted to the type of g, then converted to the type of f), as it would
be if g were not volatile, or can it be an unrelated value read back from g?
 
T

Thad Smith

Jordan said:
That's because for some reason gcc chooses to read a again. However, the
C code only includes ONE read access - to read the original value which
is incremented. R2 <- R1 would probably have been more in the spirit of
the meaning of the original expression, since the prefix ++ operator
yields a specific value, not an alias for its argument. However, since
it results in the same behavior it is permitted.

Actually it may not result in the same behavior, since volatile accesses
can have side effects outside explicit code. Volatile accesses are
often used for hardware registers. Some registers do not return the
last value written, while others might have initiate hardware actions
caused by reading the location.

As far as Standard C is concerned, ++a is equivalent to a += 1, which
returns the value of the left operand after assignment. It could be
argued that the only way to know the value of a volatile object is to
read it, even though it has just been written! In any case, what
qualifies as an access to a volatile object is implementation-defined,
and can differ from one implementation to another, but must be
documented. This gives, I think, leeway regarding how many read
accesses there are in ++a.

In summary, a conforming compiler is allowed to make another access to
a, as long as it is documented.
 
D

Daniel W

Thad said:
Actually it may not result in the same behavior, since volatile accesses
can have side effects outside explicit code. Volatile accesses are
often used for hardware registers. Some registers do not return the
last value written, while others might have initiate hardware actions
caused by reading the location.

As far as Standard C is concerned, ++a is equivalent to a += 1, which
returns the value of the left operand after assignment. It could be
argued that the only way to know the value of a volatile object is to
read it, even though it has just been written! In any case, what
qualifies as an access to a volatile object is implementation-defined,
and can differ from one implementation to another, but must be
documented. This gives, I think, leeway regarding how many read
accesses there are in ++a.

In summary, a conforming compiler is allowed to make another access to
a, as long as it is documented.

Yes, but the rationale (A.6.5.16 see original posting) clearely
states two possible rewrites of an assignment operation and the
one apliccable to ++a is given as:

(T = X(i,Sval), i = Snew(i), T)

This would indicate that the only legal rewrite is to introduce
a temporary and that the it is the value of the temporary that
is "exposed". If this is true, then gcc would be in error. I'm
not pretending to be an expert on the standard so I may be in
gross error here.

/Daniel
 
D

Daniel W

Robin said:
Daniel W wrote On 03/07/06 10:17,:
I've got a question about volatiles in assignment expressions.
I found the following code snippet in an embedded development
forum:

----
volatile char a;

...

if (++a == 1)
{
...
----

This code snippet was then followed by the output from two
compilers, one which was gcc. Gcc's code looked like something
like this (in RTL like form where Rn denotes a register):

R1 <- *a
R1 <- R1 + 1
*a <- R1
R2 <- *a
IF R2 == 1

Now to my question! The code above contains two read accesses
and one write access to a. I would have assumed that only one
read access should be performed. Does the standard allow the
second read operation?
[...]

Reading from the standard ISO/IEC [9899:1999] I find the
following passages related to this issue (A denotes the
rationale and B denotes the standard):
[...]
B.6.5.16 - 3 "An assignment operator stores a value in the
object designated by the left operand. An assignment expression
has the value of the left operand after the assignment, but is
not an lvalue. [...]

This seems to be where the ambiguity creeps in: must
"the value ... after the assignment" be read from the
volatile l.h.s. (seems to be gcc's interpretation), or
will the value assigned to the l.h.s. suffice (yours)?

The gcc reading seems defensible: "after" means "after,"
after all. Maybe the variable is really a special hardware
gadget that's connected up to an I/O device: You write a
command code to it, and when you read from it you get back
not what was written, but some kind of device status. That
is, the value as read might not resemble the value written.


The ambiguity here, if there is one, becomes more obvious if we consider f =
(g = h), with g volatile. Should the value assigned to f be that of h
(converted to the type of g, then converted to the type of f), as it would
be if g were not volatile, or can it be an unrelated value read back from g?

Whan about the rationale (A.6.5.3.1) that states that the value
of an assignment expression should not be an lvalue? A friend of
mine said that it could be interpreted such that it is the lvalue
to rvalue conversion that is the actual read operation. If so, gcc
would be in error.

/Daniel
 
D

Daniel W

Jordan said:
That's because for some reason gcc chooses to read a again. However, the
C code only includes ONE read access - to read the original value which
is incremented. R2 <- R1 would probably have been more in the spirit of
the meaning of the original expression, since the prefix ++ operator
yields a specific value, not an alias for its argument. However, since
it results in the same behavior it is permitted.

It is also my interpretation of the C code; one read and one write.
If the volatile variable is a hardware register, then the result
might not be the same and therein lies the dilemma.
Besides, the RTL code given is five statements, each presumably with its
own rtl analogue of a "sequence point", not one.




The standard does not govern gcc's "RTL" language. There is only one
read access in the C code shown.

Hmmm... The "RTL code" that I wrote was just an abstraction of the
actual instructions issued by gcc. Regardless of this, the generated
object code should match the behavior of the C virtual machine and
it is there that it seems to break down. My interpretation says one
read and one write whereas gcc produces two reads and one write.

/Daniel
 
C

Chris Torek

[gcc chooses to load, add, store, load again, and then compare,
i.e., re-read "a" after writing it in case the computed "old a
plus 1" is not still stored in "a".]

That's because for some reason gcc chooses to read a again. However, the
C code only includes ONE read access - to read the original value which
is incremented.

This is not clear, or at least, not to me. (It happens to be what
I would *prefer*, but the model in the C standards is a bit vague.)
I believe the most important sentence, however, is this one from
6.5.3 of a C99 draft:

What constitutes an access to an object that has
volatile-qualified type is implementation-defined.

In other words, gcc can do anything at all here, provided what
it does is documented.

Note that you can get the desired effect -- whichever effect is
actually desired -- with:

volatile char a;
char tmp;

tmp = a; /* one read from "a" */
a = tmp + 1; /* one write to "a" */
if (tmp == 0) /* no re-reading of "a" */

or:

tmp = a; /* one read from "a" */
a = tmp + 1; /* one write to "a" */
if (a == 1) /* re-read "a" in case it is not equal to tmp+1 */

or of course any number of equivalent code fragments.

Note also that on machines that have read/modify/write bus cycles,
there is no guarantee that operations like:

*p |= val;

use an R/M/W cycle: this might still use separate load and store
operations. If you need control that fine, you must either get
a guarantee from your implementation, or resort to assembly code.
 
R

Robin Haigh

Chris Torek said:
[gcc chooses to load, add, store, load again, and then compare,
i.e., re-read "a" after writing it in case the computed "old a
plus 1" is not still stored in "a".]

That's because for some reason gcc chooses to read a again. However, the
C code only includes ONE read access - to read the original value which
is incremented.

This is not clear, or at least, not to me. (It happens to be what
I would *prefer*, but the model in the C standards is a bit vague.)
I believe the most important sentence, however, is this one from
6.5.3 of a C99 draft:

What constitutes an access to an object that has
volatile-qualified type is implementation-defined.

In other words, gcc can do anything at all here, provided what
it does is documented.

Hmm. The value of an object is the last value stored in it (ignoring
type-punning issues here). So, if we have that to hand, we can take the
value of the object without actually accessing the object, even in the
abstract machine.

But we're told that with a volatile object, the last store need not be
explicit in the program (C99 6.2.4 p2 footnote 26 -- the standard regards a
volatile object as one which does retain its last stored value, but which
may have values stored inexplicitly between one program access and the
next).

So, the abstract machine, if it needs to take the value of a volatile
object, cannot have the last stored value handy, and must fetch the value
from the object.

And with volatiles around, the real machine must forget about as-if rewrites
and do what the abstract machine does.

There's still the bit about "what constitutes an access ... is
implementation-defined". But if the latitude given to the implementor
allows him to define read access to a volatile object as "don't bother to
look at the object, just use the last value we wrote to it", then the whole
purpose of volatile is defeated and it's all nonsense. The scope given to
the implementor must be limited by the requirement to actually obtain the
last (inexplicitly) stored value.

(This means to say: I don't know what it is exactly that's
implementation-defined, but I can't see any room within the defined
semantics for it to make a difference)


When we come to the assignment expression, that unfortunate phrase "value of
the left operand" leads me to the conclusion that (with a volatile operand)
the read access (a la gcc) is not just optional but mandatory, on the
wording as it stands.


But there's a possible get-out in the tag "after the assignment". This has
two possible interpretations:

(A) immediately after the assignment, before anything else can happen
(B) after the assignment, and possibly after further writes to the object

Is it possible that the authors, if they'd been a bit more verbose, would
have written (B)? Hard to believe. The "further writes" can't be explicit
writes, the leeway could only extend to inexplicit writes to volatile
objects. What rationale could the committee have had for wanting to include
such a proviso, which basically would conflict with the normally understood
intention of assignments being expressions with values, as represented by
expressions like a = b = c = d?

On the other hand, (A) seems to conflict with footnote 26 mentioned above,
which apparently intends to say that the last stored value of a volatile
object can never be taken as known. But maybe not, on a closer argument.

We've been given the difficult example of a hardware register that takes a
command code and returns a status code. Since 6.2.4 p2 says that all C
objects retain their last-stored values, the only way we can reconcile this
beastie with C semantics is to say that the explicit write is immediately
followed by an inexplicit write: but since the two writes are separate
sequential events, there must logically be a conceptual interval during
which the last stored value of the C object (which is an abstraction, not
actually a piece of hardware) is indeed the value we just wrote,
notwithstanding that there is effectively no corresponding hardware state.
Of course we can't access this value by readback, but the C semantics don't
require that, since we know what it is by definition anyway.

With this argument, I claim that option (A) above is possible, i.e. not in
conflict with anything else, and obviously preferable to (B). Can I get
away with this?
 
D

Daniel W

Robin said:
[given]

volatile char a;
...
if (++a == 1)

[gcc chooses to load, add, store, load again, and then compare,
i.e., re-read "a" after writing it in case the computed "old a
plus 1" is not still stored in "a".]

Jordan Abel said:
That's because for some reason gcc chooses to read a again. However, the
C code only includes ONE read access - to read the original value which
is incremented.

This is not clear, or at least, not to me. (It happens to be what
I would *prefer*, but the model in the C standards is a bit vague.)
I believe the most important sentence, however, is this one from
6.5.3 of a C99 draft:

What constitutes an access to an object that has
volatile-qualified type is implementation-defined.

In other words, gcc can do anything at all here, provided what
it does is documented.


Hmm. The value of an object is the last value stored in it (ignoring
type-punning issues here). So, if we have that to hand, we can take the
value of the object without actually accessing the object, even in the
abstract machine.

Yes, but the value of an assignment expression is not an alias
for the object assigned, i.e. the value of the assignment expression
should not be subject to any volatileness of the lvalue object? (C99
Rationale A.6.5.3.1).

The standard also states that it is the lvalue to rvalue conversion
that constitutes a read (B.6.3.2.1 p2), and if the value of an
assignment expression is not an lvalue, then no lvalue-to-rvalue
conversion can occur.
But we're told that with a volatile object, the last store need not be
explicit in the program (C99 6.2.4 p2 footnote 26 -- the standard regards a
volatile object as one which does retain its last stored value, but which
may have values stored inexplicitly between one program access and the
next).

So, the abstract machine, if it needs to take the value of a volatile
object, cannot have the last stored value handy, and must fetch the value
from the object.

And with volatiles around, the real machine must forget about as-if rewrites
and do what the abstract machine does.

There's still the bit about "what constitutes an access ... is
implementation-defined". But if the latitude given to the implementor
allows him to define read access to a volatile object as "don't bother to
look at the object, just use the last value we wrote to it", then the whole
purpose of volatile is defeated and it's all nonsense. The scope given to
the implementor must be limited by the requirement to actually obtain the
last (inexplicitly) stored value.

(This means to say: I don't know what it is exactly that's
implementation-defined, but I can't see any room within the defined
semantics for it to make a difference)

When we come to the assignment expression, that unfortunate phrase "value of
the left operand" leads me to the conclusion that (with a volatile operand)
the read access (a la gcc) is not just optional but mandatory, on the
wording as it stands.

Yet again, the C99 Rationale (A.6.5.16) gives only two possible
optimizations which, if taken as guideline on how things should
work in the non-optimized case, clearly indicates that the
intended functionality does not allow the second read. The only
possible optimization for the pre-increment case is:

(T = a + 1, a = T, T)

which do not contain the second read operation.
But there's a possible get-out in the tag "after the assignment". This has
two possible interpretations:

(A) immediately after the assignment, before anything else can happen
(B) after the assignment, and possibly after further writes to the object

Is it possible that the authors, if they'd been a bit more verbose, would
have written (B)? Hard to believe. The "further writes" can't be explicit
writes, the leeway could only extend to inexplicit writes to volatile
objects. What rationale could the committee have had for wanting to include
such a proviso, which basically would conflict with the normally understood
intention of assignments being expressions with values, as represented by
expressions like a = b = c = d?

On the other hand, (A) seems to conflict with footnote 26 mentioned above,
which apparently intends to say that the last stored value of a volatile
object can never be taken as known. But maybe not, on a closer argument.

It could also be that they wanted to say

(C) The value of an assignmend expression is the properly type
casted value assigned to the left operand
We've been given the difficult example of a hardware register that takes a
command code and returns a status code. Since 6.2.4 p2 says that all C
objects retain their last-stored values, the only way we can reconcile this
beastie with C semantics is to say that the explicit write is immediately
followed by an inexplicit write: but since the two writes are separate
sequential events, there must logically be a conceptual interval during
which the last stored value of the C object (which is an abstraction, not
actually a piece of hardware) is indeed the value we just wrote,
notwithstanding that there is effectively no corresponding hardware state.
Of course we can't access this value by readback, but the C semantics don't
require that, since we know what it is by definition anyway.

Again, I think that the rationale (A.6.5.16) gives a strong
hint that this is just the interpretation that they commitee
intended, i.e. the value *just* after passing it along the
data bus to the destination :)
With this argument, I claim that option (A) above is possible, i.e. not in
conflict with anything else, and obviously preferable to (B). Can I get
away with this?

I feel inclined to say that (A) falls closer to what I would
like the standard to say. I would prefere even more if the
wording was more like (C).

/Daniel
(who likes his theories nice and concrete)
 
R

Robin Haigh

Daniel W said:
Robin said:
[given]

volatile char a;
...
if (++a == 1)

[gcc chooses to load, add, store, load again, and then compare,
i.e., re-read "a" after writing it in case the computed "old a
plus 1" is not still stored in "a".]

That's because for some reason gcc chooses to read a again. However, the
C code only includes ONE read access - to read the original value which
is incremented.

This is not clear, or at least, not to me. (It happens to be what
I would *prefer*, but the model in the C standards is a bit vague.)
I believe the most important sentence, however, is this one from
6.5.3 of a C99 draft:

What constitutes an access to an object that has
volatile-qualified type is implementation-defined.

In other words, gcc can do anything at all here, provided what
it does is documented.


Hmm. The value of an object is the last value stored in it (ignoring
type-punning issues here). So, if we have that to hand, we can take the
value of the object without actually accessing the object, even in the
abstract machine.

Yes, but the value of an assignment expression is not an alias
for the object assigned, i.e. the value of the assignment expression
should not be subject to any volatileness of the lvalue object? (C99
Rationale A.6.5.3.1).

The standard also states that it is the lvalue to rvalue conversion
that constitutes a read (B.6.3.2.1 p2), and if the value of an
assignment expression is not an lvalue, then no lvalue-to-rvalue
conversion can occur.
But we're told that with a volatile object, the last store need not be
explicit in the program (C99 6.2.4 p2 footnote 26 -- the standard regards a
volatile object as one which does retain its last stored value, but which
may have values stored inexplicitly between one program access and the
next).

So, the abstract machine, if it needs to take the value of a volatile
object, cannot have the last stored value handy, and must fetch the value
from the object.

And with volatiles around, the real machine must forget about as-if rewrites
and do what the abstract machine does.

There's still the bit about "what constitutes an access ... is
implementation-defined". But if the latitude given to the implementor
allows him to define read access to a volatile object as "don't bother to
look at the object, just use the last value we wrote to it", then the whole
purpose of volatile is defeated and it's all nonsense. The scope given to
the implementor must be limited by the requirement to actually obtain the
last (inexplicitly) stored value.

(This means to say: I don't know what it is exactly that's
implementation-defined, but I can't see any room within the defined
semantics for it to make a difference)

When we come to the assignment expression, that unfortunate phrase "value of
the left operand" leads me to the conclusion that (with a volatile operand)
the read access (a la gcc) is not just optional but mandatory, on the
wording as it stands.

Yet again, the C99 Rationale (A.6.5.16) gives only two possible
optimizations which, if taken as guideline on how things should
work in the non-optimized case, clearly indicates that the
intended functionality does not allow the second read. The only
possible optimization for the pre-increment case is:

(T = a + 1, a = T, T)

which do not contain the second read operation.
But there's a possible get-out in the tag "after the assignment". This has
two possible interpretations:

(A) immediately after the assignment, before anything else can happen
(B) after the assignment, and possibly after further writes to the object

Is it possible that the authors, if they'd been a bit more verbose, would
have written (B)? Hard to believe. The "further writes" can't be explicit
writes, the leeway could only extend to inexplicit writes to volatile
objects. What rationale could the committee have had for wanting to include
such a proviso, which basically would conflict with the normally understood
intention of assignments being expressions with values, as represented by
expressions like a = b = c = d?

On the other hand, (A) seems to conflict with footnote 26 mentioned above,
which apparently intends to say that the last stored value of a volatile
object can never be taken as known. But maybe not, on a closer
argument.

It could also be that they wanted to say

(C) The value of an assignmend expression is the properly type
casted value assigned to the left operand
We've been given the difficult example of a hardware register that takes a
command code and returns a status code. Since 6.2.4 p2 says that all C
objects retain their last-stored values, the only way we can reconcile this
beastie with C semantics is to say that the explicit write is immediately
followed by an inexplicit write: but since the two writes are separate
sequential events, there must logically be a conceptual interval during
which the last stored value of the C object (which is an abstraction, not
actually a piece of hardware) is indeed the value we just wrote,
notwithstanding that there is effectively no corresponding hardware state.
Of course we can't access this value by readback, but the C semantics don't
require that, since we know what it is by definition anyway.

Again, I think that the rationale (A.6.5.16) gives a strong
hint that this is just the interpretation that they commitee
intended, i.e. the value *just* after passing it along the
data bus to the destination :)
With this argument, I claim that option (A) above is possible, i.e. not in
conflict with anything else, and obviously preferable to (B). Can I get
away with this?

I feel inclined to say that (A) falls closer to what I would
like the standard to say. I would prefere even more if the
wording was more like (C).

And there's another argument. Presumably it's not the intention to imply a
sequence point here. But if the wording is read as requiring two reads of a
modified object between sequence points, this is exactly analogous to a
situation that would normally produce undefined behaviour, because the
timing of the assignment is unspecified.

And if the ordering of the steps in the normal case is unspecified, then the
requirement that we must emulate the abstract machine in the presence of a
volatile object doesn't say much. As-if rewrites (where the abstract
semantics are specified, but an alternative is externally equivalent) are
banned, but any indeterminacy in the ordering of steps between sequence
points at the abstract machine level is still there.
 
D

Daniel W

Robin said:
<big snip>

And there's another argument. Presumably it's not the intention to imply a
sequence point here. But if the wording is read as requiring two reads of a
modified object between sequence points, this is exactly analogous to a
situation that would normally produce undefined behaviour, because the
timing of the assignment is unspecified.

And if the ordering of the steps in the normal case is unspecified, then the
requirement that we must emulate the abstract machine in the presence of a
volatile object doesn't say much. As-if rewrites (where the abstract
semantics are specified, but an alternative is externally equivalent) are
banned, but any indeterminacy in the ordering of steps between sequence
points at the abstract machine level is still there.

So what it boils down to is that we cannot use the value
of an assignment expression to a volatile object if we
want to guarantee the one-read-one-write scenario. Either
implementation (one read or two reads) are both within
the (fuzzy) limits of the standard.

So, if I want to be sure of the sequencing of, and number of
read operations in, the generated code, I must rely on a
temporary in the code, i.e.

t = a + 1;
a = t;
if (t /* or a */ == 1)
{
...

Many thanks
/Daniel

P.s. It's when I read texts like yours that I truly
comprehend the difference between beeing good at english
as a second language and beeing really good at english
as the primary language :)
 

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

No members online now.

Forum statistics

Threads
473,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top