This is a question about ISO C, which contains this sentence:
If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.
In ISO9899:1999 (C99) this appears in 6.7.3 Type Qualifiers.
Firstly, if we entirely removed this sentence from the text, would the behavior still be undefined? That is to say, is this re-stating an existing lack of a requirement, or is it explicitly taking away a requirement that could otherwise be inferred from elsewhere in the text? (Imagine that a sentence were added which says "whenever two values of type int
are added together whose arithmetic sum is forty-two, the behavior is undefined". Without such a sentence, the requirement for such an addition to produce a value can (and is) inferred).
Secondly, what useful behaviors does this allow an implementation to provide? That is to say, can an implementation do something beneficial which breaks programs that access something volatile
with a non-volatile
lvalue, and which therefore depends on the above sentence? Are there any implementations which do this, whatever it is?
For example, it is interesting and noteworthy that C programs can access const
-qualified objects with non-const
lvalues, just not store to them. Making the store behavior undefined is tangibly beneficial: implementations can place constants in read-only storage, and in some cases can propagate constants through a program image at translation time as if they were manifest constants. Yet, mere non-const
access is required to work. Not requiring non-const
accesses to const
objects to work would presumably be a permissiveness which brings no benefits. Yet, in the case of volatile
, it is that strict: no non-volatile
-qualified access is required to work. What is the benefit?
Can an object which is defined as volatile
by the C program itself be endowed with useful properties which break if there take place any accesses whatsoever to that object through a non-volatile
lvalue?
(Under "useful", I would specifically like to exclude diagnosis. Terminating the program upon detecting such an access, without a message, or with a message like "volatile accessed as non-volatile" doesn't count as useful; it serves only to enforce the lack of requirement.)
(Also, I'm not interested in memory mapped registers and such; strictly objects defined by the program in static, dynamic or automatic storage. Use of hardware registers is inherently nonportable.)
All I can think of is that a non-volatile
access to a volatile
object could retrieve a stale value, rather than value that was last stored
(properly, through the volatile
lvalue). This is an economic benefit in the following way: if a write occurs through, say, a volatile int
lvalue, the compiler does not have to suspect that this has any effect on any plain int
lvalue. This leads to better code generation in functions which work with mixtures volatile
and non-volatile
objects of the same type:
volatile int global;
void foo(int *p)
{
int x = *p, y;
global++;
y = *p;
}
Here, the compiler doesn't have to consider that *p
might be an alias for global
, because that usage is not required to work according to ISO C. Hence it can freely assume that the value of *p
does not change between the two references. Both x
and y
receive to the same value, derived from a single access to *p
.
If the aliasing were permitted, then the compiler would have to generate code to re-load *p
on the second access, in order that it pick up the most recent value stored in global
.
(If we remove volatile
from the above program fragment, then this is in fact the case; it has to be at least suspected, if not confirmed. that *p
may be an alias for global
and so *p
has to be re-inspected. Or else code has to be generated which compares the run-time value of p
to the address of global
and takes two different paths.)
However, we don't introduce volatile
into programs to obtain this kind of obscure optimization benefit; it is used to defeat optimization. The restrict
keyword can be used instead to allow the implementation to reason about lvalues not being aliased. It seems that the above cannot be the rationale.