Another ANSI C question about 'volatile'

T

Tim Rentsch

Here's another question related to 'volatile'. Consider
the following:

int x;

void
foo(){
int y;

y = (volatile int) x;

... further computations involving y (but no assignments to y) ...

}

My belief is that the standard allows optimizing away the variable 'y'
and re-using the value in 'x' (that is, re-loading 'x' to get the
value again). In particular, I'm thinking of the passage that says
type qualifiers are ignored except for expressions that are lvalue's,
and the casted value of 'x' isn't an lvalue.

Questions:

1. Does the language of the standard allow this optimizing away of 'y'
or not (with reasoning, please)?

2. Assuming it does, what's a better way to get the effect intended
here, namely, to cause the value of 'x' to be put in a local so
that it's guaranteed that a local copy is made and 'x' is not
referenced further?


Thanks again.
 
K

Keith Thompson

Tim Rentsch said:
Here's another question related to 'volatile'. Consider
the following:

int x;

void
foo(){
int y;

y = (volatile int) x;

... further computations involving y (but no assignments to y) ...

}

My belief is that the standard allows optimizing away the variable 'y'
and re-using the value in 'x' (that is, re-loading 'x' to get the
value again). In particular, I'm thinking of the passage that says
type qualifiers are ignored except for expressions that are lvalue's,
and the casted value of 'x' isn't an lvalue.

Questions:

1. Does the language of the standard allow this optimizing away of 'y'
or not (with reasoning, please)?

2. Assuming it does, what's a better way to get the effect intended
here, namely, to cause the value of 'x' to be put in a local so
that it's guaranteed that a local copy is made and 'x' is not
referenced further?

I think you're looking for:

volatile int y = x;

Why do you want to do this?
 
T

Tim Rentsch

Keith Thompson said:
Tim Rentsch said:
int x;

void
foo(){
int y;

y = (volatile int) x;

... further computations involving y (but no assignments to y) ...

}

[snipped]

I think you're looking for:

volatile int y = x;

Why do you want to do this?

Sorry I wasn't more clear on this. The idea is to force the compiler
to use an actual local variable to hold the value of 'x', but then
allow that variable to be optimized freely. Kind of a "locally
treat 'x' as volatile even though it isn't declared that way."
Does that make sense? I realize it isn't really an answer to
the "Why" question, but hopefully it clarifies things enough
so that a means can be suggested.

thanks again.
 
C

Chris Torek

Sorry I wasn't more clear on this. The idea is to force the compiler
to use an actual local variable to hold the value of 'x', but then
allow that variable to be optimized freely. Kind of a "locally
treat 'x' as volatile even though it isn't declared that way."
Does that make sense?

Scarily, yes :)

This is not what I would consider "good style" but will achieve
the effect:

int x;
...
void f(void) {
int y = *(volatile int *)&x;
... use y as needed ...
}

Equivalently, and perhaps slightly better (but still not "great")
style:

void f(void) {
/*
* NOTE TO MAINTAINERS: references through *xp read or write
* x as if x were declared volatile, even though it is not.
* This can be used to trick the compiler into doing operations
* with partial ordering constraints without having total
* ordering constraints.
*/
volatile int *xp = &x;
int y = *xp;
... use y and/or *xp as needed ...
}

Fundamentally, the problem is that things like Partial Store Order
or Relaxed Memory Order multiprocessing models do not fit well with
the C language -- C is too high-level for these. Depending on
one's compiler, though, it may be possible to insert memory barriers
and convince the compiler not to move memory operations across
those barriers, while permitting all optimizations that do not
cross such barriers. (With gcc, use "__asm__ volatile" and include
"memory" in the set of "clobbered" operands, for instance.)

This is all *way* outside the realm of Standard C, which does not
support multiprocessing at all, and has quite limited support for
interruptions (such as signals) in a single-processing model.
 
T

Tim Rentsch

Chris Torek said:
[lots of earlier stuff omitted]

This is not what I would consider "good style" but will achieve
the effect:

int x;
...
void f(void) {
int y = *(volatile int *)&x;
... use y as needed ...
}

Equivalently, and perhaps slightly better (but still not "great")
style:

void f(void) {
/*
* NOTE TO MAINTAINERS: references through *xp read or write
* x as if x were declared volatile, even though it is not.
* This can be used to trick the compiler into doing operations
* with partial ordering constraints without having total
* ordering constraints.
*/
volatile int *xp = &x;
int y = *xp;
... use y and/or *xp as needed ...
}

Thank you Chris for these helpful replies.

I'd thought of the 'y = *(volatile int *)&x;' before, but I concluded
it had the same problem as 'y = (volatile int)x;' -- because the
subexpression '(volatile int *)&x' isn't an lvalue (by which I mean,
can't be on the left hand side of an assignment operator), the
'volatile' type qualifier is discarded. Or do I misunderstand
what the standard means by 'lvalue'?

The other solution, with 'volatile int *xp = &x;', seems to
work just fine. That's great, I hadn't thought of that.

Incidentally, what about this idea:

int * volatile xp = &x, y = *xp;

Seems like that also would force actual evaluation into a
'y' temporary, even imagining a compiler smart enough to
figure out that 'y' is getting the value in 'x'. Yes?

Fundamentally, the problem is that things like Partial Store Order
or Relaxed Memory Order multiprocessing models do not fit well with
the C language -- C is too high-level for these. Depending on
one's compiler, though, it may be possible to insert memory barriers
and convince the compiler not to move memory operations across
those barriers, while permitting all optimizations that do not
cross such barriers. (With gcc, use "__asm__ volatile" and include
"memory" in the set of "clobbered" operands, for instance.)

I don't think of this as a memory barrier issue. I want to capture
the value that the global 'x' has at the beginning of the function,
and guarantee that exactly that same value is used later in the
function. Any changes that happen to 'x', for whatever reason, while
the function is running must not be allowed to affect what the
function does, which should depend on the value that 'x' has at function
start and not any subsequent values.

This is all *way* outside the realm of Standard C, which does not
support multiprocessing at all, and has quite limited support for
interruptions (such as signals) in a single-processing model.

Is it? I thought one of the reasons for having 'volatile' is
to enable such things as this in operating system code, which
is in fact the area of application in this case.

Thanks again for the helpful response.
 
J

Jack Klein

Chris Torek said:
[lots of earlier stuff omitted]

This is not what I would consider "good style" but will achieve
the effect:

int x;
...
void f(void) {
int y = *(volatile int *)&x;
... use y as needed ...
}

Equivalently, and perhaps slightly better (but still not "great")
style:

void f(void) {
/*
* NOTE TO MAINTAINERS: references through *xp read or write
* x as if x were declared volatile, even though it is not.
* This can be used to trick the compiler into doing operations
* with partial ordering constraints without having total
* ordering constraints.
*/
volatile int *xp = &x;
int y = *xp;
... use y and/or *xp as needed ...
}

Thank you Chris for these helpful replies.

I'd thought of the 'y = *(volatile int *)&x;' before, but I concluded
it had the same problem as 'y = (volatile int)x;' -- because the
subexpression '(volatile int *)&x' isn't an lvalue (by which I mean,
can't be on the left hand side of an assignment operator), the
'volatile' type qualifier is discarded. Or do I misunderstand
what the standard means by 'lvalue'?

Yes, you did understand what is meant by 'lvalue' in this case.

The cast is on the address of x. The result of the cast is indeed an
lvalue of type 'pointer to volatile int'. The pointer itself is not
volatile, which would indeed be meaningless. But the type pointed to
by the pointer is not an lvalue, and that is what the 'volatile' in
the cast applies to.
The other solution, with 'volatile int *xp = &x;', seems to
work just fine. That's great, I hadn't thought of that.

Notice that the overall effect of this statement is no different than
the other one. In fact, you could emphasize the similarity by adding
a dreaded redundant cast to this expression:

volatile int *xp = (volatile int *)&x;

Note that the automatic conversion performed by the assignment is
exactly the same as that of the first cast expression.
Incidentally, what about this idea:

int * volatile xp = &x, y = *xp;

No, this does not do the same thing at all. Type qualifiers 'const'
and 'volatile' (and probably 'restrict', but I haven't checked) modify
what appears to their left. In this case you are defining 'xp' as a
volatile pointer to a non-volatile int.

Technically that should mean that the compiler must read the value of
the pointer xp every time you dereference it, rather than relying on a
cached value in a register or whatever. Common sense would indicate
that a compiler, needing to reread xp each time, would then
dereference it to access memory each time, but the standard does not
require this behavior.

You are unlikely to find one, but since the integer pointed to by xp
is not volatile, a compiler would be within its rights to read the
value of xp each time, compare it to a cached value, and use a cached
value for '*xp' if 'xp' itself is still the same as the last time it
was dereferenced.
Seems like that also would force actual evaluation into a
'y' temporary, even imagining a compiler smart enough to
figure out that 'y' is getting the value in 'x'. Yes?



I don't think of this as a memory barrier issue. I want to capture
the value that the global 'x' has at the beginning of the function,
and guarantee that exactly that same value is used later in the
function. Any changes that happen to 'x', for whatever reason, while
the function is running must not be allowed to affect what the
function does, which should depend on the value that 'x' has at function
start and not any subsequent values.

Any compiler that did not behave the way you describe would be
horribly broken. Don't worry about it.
Is it? I thought one of the reasons for having 'volatile' is
to enable such things as this in operating system code, which
is in fact the area of application in this case.

What makes you think that the compiler is trying to optimize away a
local variable that stores the value of a file-scope object? In any
case, if the value of 'x' is subject to change while a function is
executing, and that function does not write to 'x', write via a
pointer that might alias to 'x', or call another function that might
modify 'x', that means that the value of 'x' might change
asynchronously and beyond the scope of the compiler.

In that case, 'x' should be defined as volatile in the first place.
 
K

Keith Thompson

Tim Rentsch said:
I don't think of this as a memory barrier issue. I want to capture
the value that the global 'x' has at the beginning of the function,
and guarantee that exactly that same value is used later in the
function. Any changes that happen to 'x', for whatever reason, while
the function is running must not be allowed to affect what the
function does, which should depend on the value that 'x' has at function
start and not any subsequent values.

Maybe I'm oversimplifying, but I would think that simply assigning the
value to a local variable would do the trick. For example:

int x;

void foo()
{
int y = x;
...
}

References to y within foo should refer to x *only* if the compiler
can prove that they have the same value at that point -- unless the
compiler is badly broken.

The only exception would be if x can be changed by some external
mechanism that the compiler doesn't know about; in that case,
shouldn't x just be declared volatile?

I get the feeling I'm missing something.
 
T

Tim Rentsch

Jack Klein said:
On 15 Aug 2004 20:40:44 -0700 said:
[stuff omitted]

I'd thought of the 'y = *(volatile int *)&x;' before, but I concluded
it had the same problem as 'y = (volatile int)x;' -- because the
subexpression '(volatile int *)&x' isn't an lvalue (by which I mean,
can't be on the left hand side of an assignment operator), the
'volatile' type qualifier is discarded. Or do I misunderstand
what the standard means by 'lvalue'?

Yes, you did understand what is meant by 'lvalue' in this case.

The cast is on the address of x. The result of the cast is indeed an
lvalue of type 'pointer to volatile int'. The pointer itself is not
volatile, which would indeed be meaningless. But the type pointed to
by the pointer is not an lvalue, and that is what the 'volatile' in
the cast applies to.

Here is my problem. The result of the cast is not an "lvalue" as I am
used to the term. Reason being, writing an assignment statement that
starts

(volatile int *) &x = ...

isn't legal. Of course it is legal if there is a '*' in front of the
cast, but that's a different expression. My best understanding now
is that the expression '(volatile int *) &x' is an 'lvalue' as the
term is used (and defined) within the C standard. If this expression
is an lvalue, then the 'volatile' qualifier is retained, and
everything works fine. And -- as I just found out -- the Rationale
has a supporting statement along these lines:

If it is necessary to access a non-volatile object using
volatile semantics, the technique is to cast the address
of the object to the appropriate pointer-to-qualified type,
then dereference the pointer.

So there you have it.

No, this does not do the same thing at all. Type qualifiers 'const'
and 'volatile' (and probably 'restrict', but I haven't checked) modify
what appears to their left. In this case you are defining 'xp' as a
volatile pointer to a non-volatile int.

Technically that should mean that the compiler must read the value of
the pointer xp every time you dereference it, rather than relying on a
cached value in a register or whatever. Common sense would indicate
that a compiler, needing to reread xp each time, would then
dereference it to access memory each time, but the standard does not
require this behavior.

You are unlikely to find one, but since the integer pointed to by xp
is not volatile, a compiler would be within its rights to read the
value of xp each time, compare it to a cached value, and use a cached
value for '*xp' if 'xp' itself is still the same as the last time it
was dereferenced.

Yes, I realized this declaration was declaring the pointer as volatile
rather than what it points to. But you're right, I didn't expect that
a particularly perverse compiler could circumvent that and still be
conformant. A very interesting point - thank you.

Any compiler that did not behave the way you describe would be
horribly broken. Don't worry about it.


What makes you think that the compiler is trying to optimize away a
local variable that stores the value of a file-scope object?

Sadly, because it appears that this has happened in similar cases
and that caused difficult-to-discover bugs.

As for the compiler being broken - perhaps it is, but my belief is (if
there is no mention of 'volatile') the standard allows the compiler to
behave in this way and still be conformant.

In any
case, if the value of 'x' is subject to change while a function is
executing, and that function does not write to 'x', write via a
pointer that might alias to 'x', or call another function that might
modify 'x', that means that the value of 'x' might change
asynchronously and beyond the scope of the compiler.

In that case, 'x' should be defined as volatile in the first place.

Oh, no argument there. It wasn't because of "practical considerations."
If you know what I mean.....

thanks again for the help!
 
K

Keith Thompson

Tim Rentsch said:
Sadly, because it appears that this has happened in similar cases
and that caused difficult-to-discover bugs.

As for the compiler being broken - perhaps it is, but my belief is (if
there is no mention of 'volatile') the standard allows the compiler to
behave in this way and still be conformant.

Given:

int x;

void foo(void)
{
int y = x;
/* ... */
... reference to y ...
}

the code generated for the reference to y can refer to y, to x, or to
the current phase of the Moon, as long as it gets the same result it
would get by actually referring to y. If references to global
variables are quicker than references to local variables (which seems
unlikely), a decent optimizing compiler is likely to transform
references to y into references to x, as long as it can prove that it
will get the same result.

In this case:

int x;

void foo(void)
{
int y = x;
x ++;
/* ... *
... reference to y ...
}

the compiler *cannot* perform this transformation; if it does, it's
violating the standard.

The transformation is forbidden in this case because x was modified by
the program itself. If the modification is done by some outside
entity that the compiler can't be expected to know about, that's a
different story; if that can happen, x should be declared volatile.
 

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,777
Messages
2,569,604
Members
45,217
Latest member
topweb3twitterchannels

Latest Threads

Top