return x = 0; // under C99 for volatile x

T

Tim Rentsch

Francois Grieu said:
Hi, consider:

volatile unsigned char x;
unsigned char foo(void) { return x = 0; }

Assuming conformant C99, when foo is executed, is the value returned
a) allways 0
b) what was read from x after x has been written with 0
c) unspecified.

This is a murky area as to what the Standard actually mandates.
Furthermore it is complicated by the implementation-definedness
of "what constitutes an access to a volatile-qualified" object;
writing 'return x = 0;' is likely to lead to different results
with different compilers. Better to spell it out explicitly,
that is, write either

return x = 0, x;

or

unsigned char t;
return x = t = 0, t;

(or something similar to one of the above depending on personal
stylistic preferences).
 
T

Tim Rentsch

Seebs said:
volatile int buffer_ready;
char buffer[BUF_SIZE];
void buffer_init() {
int i;
for (i=0; i<BUF_SIZE; i++)
buffer = 0;
buffer_ready = 1;
}
"The for-loop does not access any volatile locations, nor does it
perform any side-effecting operations. Therefore, the compiler is free
to move the loop below the store to buffer_ready, defeating the
developer's intent."
Hmmm. Interesting. Well, first off, I'd point out that unless there's more
to it, buffer is obviously all zeroes already, making the question moot.

That's not obvious at all. We don't have any idea what else happens
before the call to buffer_init().

As I said, "unless there's more to it". If there's other code affecting
these, it's hard to guess. Without other code, though, it's moot.
What is obvious is that the first quoted sentence is just plain
false, and therefore the second sentence's 'therefore' does not
necessarily follow, and indeed cannot follow.

Agreed. It is pretty obvious that the loop performs assignment, which is
an operation with side effects.

That said:

In general, a compiler is permitted to reorder side effects on non-volatiles
as long as a strictly conforming program can't tell the difference. So,
if you ran this on a threaded system, it's not obvious to me that the compiler
couldn't reorder the side effects on the buffer to later in the function,


Again this is a murky area because of the implementation-definedness
of what constitutes a volatile-qualified access. However, for any
reasonably sensible such definition, the side effects on the buffer
would have to be done before the volatile access, because access to
volatile-qualified objects must be done strictly according to the
abstract semantics, which includes completing all side effects of
all previous expressions.
because a strictly conforming program has no way to peek at the buffer
and the volatile flag until the function returns... [snip rest]

Any program that uses 'volatile' is affected both by unknown
side effects and the implementation-defined aspects as to what
constitutes a volatile-qualified access; such a program therefore
can never be strictly conforming.
 
L

lawrence.jones

Kaz Kylheku said:
An assignment expression has the value of the left operand, after
the assignment (and not: the value that is stored in the operand
/by/ the assignment).

Access to a volatile object is a side effect.

So, after zero is stored in it, the object x must be accessed again to
retrieve the stored value.

That's a common misconception, it was not the committee's intent. The
committee intended that "after" could be taken as "immediately after",
in which case the compiler can infer the value without having to refetch
it (although it's certainly allowed to refetch if it wants).
 
K

Keith Thompson

That's a common misconception, it was not the committee's intent. The
committee intended that "after" could be taken as "immediately after",
in which case the compiler can infer the value without having to refetch
it (although it's certainly allowed to refetch if it wants).

What if the value of the RHS expression of the assignment is never
actually stored in the object, or at least can never be retrieved
from the object?

For example:

volatile int x;

x = 42;

Isn't it possible for an implementation to define "volatile" in such a
way that assigning the value 42 to x has some particular
implementation-defined effect, but that affect doesn't include storing
the value 42 in x?

Must this:
int foo(void) { return x = 42; }
have the same effect as this:
int foo(void) { x = 42; return x; }
?
 
K

Kaz Kylheku

That's a common misconception, it was not the committee's intent.

There is no misconception. The text clearly says that ``An assignment
expression has the value of the left operand after the assignment, but is not
an lvalue.''
The
committee intended that "after" could be taken as "immediately after",

This is a mostly meaningless distinction. Either two events A and B are
simultaneous or either A is after B or vice versa.

``Immediately after'' is some subjective judgment which usually means that one
event follows the other such that there are not intervening events (at least no
intervening events from among class of events that are relevant to the
situation: A happens after B such that nothing relevant happens in between,
hence A happens immediately after B.

But there does not have to be an intervening event in order for the value of
the object to be different from what was stored.

For instance, if the lvalue denotes a memory-mapped register, the register
could have the semantics of control on write, and status on read. At no time
does the register ever play back the control word.

Thus ``value of the left operand'' is always, unabiguously, the status word,
regardless of how soon after the write the value is sampled.
in which case the compiler can infer the value without having to refetch

Inferring the value without refetching it means that the value is not in fact
that of the left operand after the assignment.

Of course, this is is an optimization which is permitted in the absence of
volatile.

For a volatile object, this optimization is not permitted by the text
which requires that the expression has the value of the left operand.
it (although it's certainly allowed to refetch if it wants).

So now you are saying that the committee intent is to actually de-clarify the
clear text. What may be implemented is either the straightforward
interpretation, or another one.

This is puzzling. See, usually statements of intent do the opposite: there is a
text with two or more possible meanings and the clarification of intent makes
it clear which one it is. So here there is a text with one meaning, and the
intent is to have two meanings? Hmm.

Do you have a copy of some ISO correspondence related to this?
 
L

lawrence.jones

Keith Thompson said:
What if the value of the RHS expression of the assignment is never
actually stored in the object, or at least can never be retrieved
from the object?

The abstract machine doesn't admit to such possibilities. A volatile
object is still an object with normal object semantics (e.g., it retains
its last stored value), it's just that its value can be stored in ways
unknown to the implementation and accessing it may have additional side
effects. Something like a memory mapped i/o port where the value read
has nothing whatsoever to do with the value previously stored still fits
that model if you assume that the abstract object's value is modified by
an unknown external agent right after each store, but an implementation
is allowed to decide that the value of an assignment to the object is
the value before that external agent gets a chance to modify it.
Must this:
int foo(void) { return x = 42; }
have the same effect as this:
int foo(void) { x = 42; return x; }

No, it's also permitted to have the same effect as:
int foo(foid) { x = 42; return 42; }

The only reason for the peculiar wording was to handle the cases where
the value is changed by the assignment in ways that aren't obvious from
the overt type of the left hand side (e.g., the value is truncated when
assigned to a small bit-field).
 
T

Tim Rentsch

Kaz Kylheku said:
There is no misconception. The text clearly says that ``An assignment
expression has the value of the left operand after the assignment, but is not
an lvalue.''


This is a mostly meaningless distinction. Either two events A and B are
simultaneous or either A is after B or vice versa.

``Immediately after'' is some subjective judgment which usually means that one
event follows the other such that there are not intervening events (at least no
intervening events from among class of events that are relevant to the
situation: A happens after B such that nothing relevant happens in between,
hence A happens immediately after B.

But there does not have to be an intervening event in order for the value of
the object to be different from what was stored.

For instance, if the lvalue denotes a memory-mapped register, the register
could have the semantics of control on write, and status on read. At no time
does the register ever play back the control word.

Thus ``value of the left operand'' is always, unabiguously, the status word,
regardless of how soon after the write the value is sampled.


Inferring the value without refetching it means that the value is not in fact
that of the left operand after the assignment.

Of course, this is is an optimization which is permitted in the absence of
volatile.

For a volatile object, this optimization is not permitted by the text
which requires that the expression has the value of the left operand.


So now you are saying that the committee intent is to actually de-clarify the
clear text. What may be implemented is either the straightforward
interpretation, or another one.

This is puzzling. See, usually statements of intent do the opposite: there is a
text with two or more possible meanings and the clarification of intent makes
it clear which one it is. So here there is a text with one meaning, and the
intent is to have two meanings? Hmm.

First let me say that I share some of these reactions to the
existing wording. The "plain meaning" seems to require a write
and a subsequent read for a volatile-qualified access. Worse,
the description written is just vague enough so that the point is
debatable -- always a bad sign.

Having said that, the Standard does contain a clear out that
allows either interpretation (reading after assigning, or not
reading after assigning) to be chosen by an implementation,
namely, the proviso that what constitutes a volatile-qualified
access is implementation-defined. For better or worse, that
clause provides enough wiggle room so that an implementation can
choose either interpretation -- and, as far as I can tell, not
even necessarily consistently from one assignment to the next.
Rather a sad state of affairs, but given how things are it seems
best to accept the potential ambiguity and just avoid using the
result of a volatile-qualified assignment in cases where which of
the two possibilities is chosen might make a difference.
 
P

Phil Carmody

That's a common misconception, it was not the committee's intent.

It's not a misconception, it's an interpretation of what the committee
wrote. All other interpretations require horrible hand-waving, flapping
and pleading with the audience.

If it was not the committee's intent, then the committee desperately
needs to reword the sentence.
The
committee intended that "after" could be taken as "immediately after",
in which case the compiler can infer the value [...]

I don't see 'inferring' as being compatible with 'volatile'. Volatile
means _no_ short cuts, even if two things are immediately after each
other:

extern volatile int flag;
if(flag^flag) unbrundle();

Are the two accesses to flag not immediately after each other?
Yet no-one in their right might would infer that the two accesses
could be reduced to one.

So, the way you've expressed it here, I think the committee needs
to change its intentions, rather than the wording of the paragraph.

Phil
 
K

Kaz Kylheku

The abstract machine doesn't admit to such possibilities. A volatile
object is still an object with normal object semantics (e.g., it retains

No it is not. This is completely a wrong path to take regarding
the meaning of an assignment expression; it does not rest on the
semantics of volatile, but on the semantics of ``assignment expression''.
its last stored value), it's just that its value can be stored in ways

The volatile qualifier expresses precisely the opposite idea: that the object
does not have normal object semantics: that it cannot be relied upon to retain
its last stored value.

Caching optimizations rely on objects retaining their last stored value,
so that the cached copies can be trusted to be coherent with the original.

volatile defeats such optimizations for objects which are known, or suspected
of violating this coherency requirement.
The only reason for the peculiar wording was to handle the cases where
the value is changed by the assignment in ways that aren't obvious from
the overt type of the left hand side (e.g., the value is truncated when
assigned to a small bit-field).

There are other ways to express such an intent
without actually saying that the value of the expression /is/ that of
the left operand.

Now let's think about this. Suppose that the lvalue foo.three is
an unsigned bit-field, three bits wide, and suppose that it's
a normal object which retains its last stored value.

If we perform this expression:

return foo.three = 42;

the value 42 is reduced modulo 8 into the range 0-7, and that resulting value
is also returned.

Why does assignment return this reduced value?

Argument: it's could be because the value is understood as going into the
object, and then emerging again to form the result.

If the value does not go into the object, there is no reason to reduce
it to three bits; the assignment expression might as well yield 42.

I.e. the right hand side is shunted into the object, where it must be reduced,
/and/ it is also returned.

For that matter, the resulting value can have the original type, too.

Someone designing a language in which the assignment /forks/ the value into two
destinations might not design it this way. The resulting value would be the
original value of the right hand side, of the original type. That's a much
more sensible design: why introduce a potentially dangerous conversion where
one isn't needed?

For instance, we could have a C dialect in which the following initializes
the two real values to 3.14:

double1 = double2 = integer = 3.14;

The 3.14 is truncated when it is shunted into the integer, but the
result of (integer = 3.14) is of type double, so the original 3.14 (or best
approximation of that constant in the double type) propagates to real2 and
real1. Basically, the initialization is fully paralellizeable.

If you want an alternate rationale for the strange wording which
supports your case, how about this: it is necessary because without it,
expressions like the following would be undefined behavior:

node = node->next = NULL;

The NULL could race ahead and get stored into node, turning node->next
into a null dereference. The wording ``value of the left operand after the
assignment'' introduces a data flow constraint which allows expressions like
the above to be harmless and even useful.
 
K

Kaz Kylheku

Having said that, the Standard does contain a clear out that
allows either interpretation (reading after assigning, or not
reading after assigning) to be chosen by an implementation,
namely, the proviso that what constitutes a volatile-qualified
access is implementation-defined.

The implementation-defined constitution of a volatile access in no way permits
an actual access to be omitted when an abstract access is called for to a
volatile object.

The ultimate conclusion if this interpretation is that any and all volatile
access can be optimized in exactly the same way as regular accesses,
which is preposterous. An implementation cannot simply say that ``Sometimes,
just to thumb our noses at the programmer, a volatile access is constituted of
nothing: our compiler infers the value without consulting the object.''

If this were the case, then the standard-blessed volatile sig_atomic_t trick in
a singal handler wouldn't work, and volatile would not ensure the proper
behavior of modified local variables between a setjmp and longjmp.

Cleary, a failure to access the object at all cannot possibly constitute any
kind of access to that object! That is just nonsense and anyone who implements
along those lines will not produce a viable tool, even if it may be regarded as
conforming. (The /bin/true program on Unix can be regarded as a conforming
impelmentation of C90, too, due to poor wording in the standard).

The authors of the paper _Volatiles Are Miscompiled, and What to Do about It_
looked hard, and up the most plausible reason for the implementation-defined
constitution of a volatile access. They provide the following reference to a
Usenet posting by Doug Gwyn, who was on the standardization committee for C90:

Douglas Gwyn. NEVERMIND! (was: Re: A question on volatile
accesses). USENET post to comp.std.c, November 1990.
http://groups.google.com/group/comp.std.c/msg/7709e4162620f2cd.

Chasing that reference:

``However, although it was proposed that conforming implementations be
required to implement the minimum possible access "width" for
volatile-qualified data, and that is the intent of requiring an
implementation definition for it, it was not practical to insist on it
in every implementation; thus, some latitude was allowed implementors
in that regard.''

See there is the question of width. If accessing an object has side effects,
there is the issue that a C lvalue doesn't necessarily correspond to the
addressable range that is tied to the same effect. Accessing an unsigned char
x[0] may produce the a side effect related also to adjacent x[1].
So in fact, the access to x[0] constitutes not only an access to x[0]
but an access to x[1], and possibly elsewhere.
 
T

Tim Rentsch

Kaz Kylheku said:
No it is not. This is completely a wrong path to take regarding
the meaning of an assignment expression; it does not rest on the
semantics of volatile, but on the semantics of ``assignment expression''.

I think what Larry meant was it's still a normal object as far
as the abstract machine is concerned. In the abstract machine
all accesses get performed and none are ever optimized away
whether the reference in question is volatile-qualified or not.

its last stored value), it's just that its value can be stored in ways

The volatile qualifier expresses precisely the opposite idea: that the object
does not have normal object semantics: that it cannot be relied upon to retain
its last stored value. [snip elaboration]

The two sets of comments here use the word "stored" in two different
ways. The '>>' comments used "stored" to mean "stored by any machine
or hardware action whatsoever"; the '>' comments use "stored" to mean
"stored by the C program under consideration". That inconsistency
needs to be cleared up before the response is meaningful.

There are other ways to express such an intent
without actually saying that the value of the expression /is/ that of
the left operand.

Now let's think about this. Suppose that the lvalue foo.three is
an unsigned bit-field, three bits wide, and suppose that it's
a normal object which retains its last stored value.

If we perform this expression:

return foo.three = 42;

the value 42 is reduced modulo 8 into the range 0-7, and that resulting value
is also returned.

Why does assignment return this reduced value?

Argument: it's could be because the value is understood as going into the
object, and then emerging again to form the result.

An explanation more consistent with what the standard says
is that the RHS value is converted to the type of the LHS
prior to assignment.
If the value does not go into the object, there is no reason to reduce
it to three bits; the assignment expression might as well yield 42.

The rules for type conversion explain why the value is reduced
modulo 8, because it has been converted -- notice, prior to
storing it -- to a three-bit unsigned type. For whatever
reason the Standard is rather vague in talking about the
types of bitfields (perhaps because those types can't be
typedef'ed?), but they behave just as though they are
integer types of limited precision (that is, they don't
have to match the precision of any integer types that
can be used for non-bitfield variables).
I.e. the right hand side is shunted into the object, where it must be reduced,
/and/ it is also returned.

For that matter, the resulting value can have the original type, too.

Yes, certainly in some other language it could, but in C
it doesn't (assuming the LHS type is different from the
RHS type); in C the RHS value is always converted to
the type of the LHS value, and the type of an assignment
expression is (the unqualified version of) the LHS type.
Someone designing a language in which the assignment /forks/ the value into two
destinations might not design it this way. The resulting value would be the
original value of the right hand side, of the original type. That's a much
more sensible design: why introduce a potentially dangerous conversion where
one isn't needed?

For instance, we could have a C dialect in which the following initializes
the two real values to 3.14:

double1 = double2 = integer = 3.14;

The 3.14 is truncated when it is shunted into the integer, but the
result of (integer = 3.14) is of type double, so the original 3.14 (or best
approximation of that constant in the double type) propagates to real2 and
real1. Basically, the initialization is fully paralellizeable.

Arguably that would be a better design choice. However, it's
a different design choice than the choice that was made for C.

If you want an alternate rationale for the strange wording which
supports your case, how about this: it is necessary because without it,
expressions like the following would be undefined behavior:

node = node->next = NULL;

The NULL could race ahead and get stored into node, turning node->next
into a null dereference. The wording ``value of the left operand after the
assignment'' introduces a data flow constraint which allows expressions like
the above to be harmless and even useful.

I find Larry's explanation more plausible. Because the Standard
is vague about the types of bit-fields, it was necessary to
put in wording like that in 6.5.16p3 in order to require the
semantics that the standards committee intended the assignment
operator to have, namely, the value that comes out is the
same as the value that is actually stored.
 
T

Tim Rentsch

Kaz Kylheku said:
The implementation-defined constitution of a volatile access in no way permits
an actual access to be omitted when an abstract access is called for to a
volatile object.

Other people feel differently.
The ultimate conclusion if this interpretation is that any and all volatile
access can be optimized in exactly the same way as regular accesses,

Yes, in fact, some people who have a lot of experience dealing
with standard C and the C standards committee apparently believe
just that, and that the committee agrees with them.
which is preposterous.

I wasn't meaning to pass judgment one way or the other,
only to relay my best understanding of the community's
consensus.
An implementation cannot simply say that ``Sometimes,
just to thumb our noses at the programmer, a volatile access is constituted of
nothing: our compiler infers the value without consulting the object.''

My understanding (and admittedly I am not omniscient) is that
the committee means that implementations may do just that.
And I know of at least one implementation group that
reached the same conclusion. (I can't say which one for
reasons of confidentiality obligations.)
If this were the case, then the standard-blessed volatile sig_atomic_t trick in
a singal handler wouldn't work, and volatile would not ensure the proper
behavior of modified local variables between a setjmp and longjmp.

I think your reasoning here is a little bit off. The clauses
regarding volatile for setjmp/longjmp and sig_atomic_t don't
depend on what constitutes a volatile access, only that certain
variables be declared volatile in order for those things not
to misbehave. The implementation-definedness of volatile
access doesn't affect those one way or the other.
Cleary, a failure to access the object at all cannot possibly constitute any
kind of access to that object! That is just nonsense and anyone who implements
along those lines will not produce a viable tool, even if it may be regarded as
conforming.

Yes, I sympathize, but it's important to realize that these two
concerns are separate, namely, is this a compiler someone might
want to use, and is this compiler conforming? The question that
was asked is about conforming compilers, not necessarily limited
just to reasonable compilers, and that question is the one I was
meaning to address.
(The /bin/true program on Unix can be regarded as a conforming
impelmentation of C90, too, due to poor wording in the standard).

Cool. :) Do you happen to know what non-trivial program
it compiles and executes (since it has to be able to for
at least one)?
The authors of the paper _Volatiles Are Miscompiled, and What to Do about It_
looked hard, and up the most plausible reason for the implementation-defined
constitution of a volatile access.

Yes, I've read that paper. I think some of the details they
get wrong in a few cases, but it definitely seems like a good
paper. I hope there is more work published in this area.
They provide the following reference to a
Usenet posting by Doug Gwyn, who was on the standardization committee for C90:

Douglas Gwyn. NEVERMIND! (was: Re: A question on volatile
accesses). USENET post to comp.std.c, November 1990.
http://groups.google.com/group/comp.std.c/msg/7709e4162620f2cd.

Chasing that reference:

``However, although it was proposed that conforming implementations be
required to implement the minimum possible access "width" for
volatile-qualified data, and that is the intent of requiring an
implementation definition for it, it was not practical to insist on it
in every implementation; thus, some latitude was allowed implementors
in that regard.''

I read the full message. As far as I can tell it's consistent
with my understanding and with the comments I've made here.

See there is the question of width. If accessing an object has side effects,
there is the issue that a C lvalue doesn't necessarily correspond to the
addressable range that is tied to the same effect. Accessing an unsigned char
x[0] may produce the a side effect related also to adjacent x[1].
So in fact, the access to x[0] constitutes not only an access to x[0]
but an access to x[1], and possibly elsewhere.

Yes, certainly that is one factor. But notice that Doug says
that they didn't go so far as to codify that, because doing
so "was not practical to insist on it for every implementation".
So what they ended up doing was punting on the problem, making
it implementation-defined without imposing any specific limits
as to what the definition could or could not say about what
a volatile-qualified access is.
 
L

lawrence.jones

Kaz Kylheku said:
The implementation-defined constitution of a volatile access in no way permits
an actual access to be omitted when an abstract access is called for to a
volatile object.

The ultimate conclusion if this interpretation is that any and all volatile
access can be optimized in exactly the same way as regular accesses,
which is preposterous.

On the contrary, it's quite reasonable for an implementation that has no
desire to support any kind of concurrency to do exactly that. Of
course, it would be foolish for someone who wants concurrency to use
such an implementation, which is why it's required to be documented.
Don't forget that volatile comes from the very early days of the ANSI C
Standard, long before threading was as common as it is today. Volatile
was a hook for those implementations that wanted it, it was never
intended to force implementations to support concurrency whether they
wanted to or not.
 
P

Phil Carmody

On the contrary, it's quite reasonable for an implementation that has no
desire to support any kind of concurrency to do exactly that. Of
course, it would be foolish for someone who wants concurrency to use
such an implementation, which is why it's required to be documented.
Don't forget that volatile comes from the very early days of the ANSI C
Standard, long before threading was as common as it is today. Volatile
was a hook for those implementations that wanted it, it was never
intended to force implementations to support concurrency whether they
wanted to or not.

Kaz doesn't mention concurrency at all, why do you focus on it so?

Phil
 
L

lawrence.jones

Phil Carmody said:
Kaz doesn't mention concurrency at all, why do you focus on it so?

Because without some kind of concurrency (whether real or virtual),
there's no way to tell whether volatile does anything or not.
 
A

Antoninus Twink

Because without some kind of concurrency (whether real or virtual),
there's no way to tell whether volatile does anything or not.

Looking at the assembly code produced by the compiler usually gives you
a pretty good idea.
 

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,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top