The key phrase, at least from the C specification is:
"An object that has volatile-qualified type may be modified in
ways unknown to the implementation or have other unknown side
effects"
Which says a lot more than volatile is a no-op, at least for
those of us who write device drivers!
Certainly, and while it's hard to read any formal, normative
meaning into that statement, it's certainly a very clear
statement of intent.
IIRC, it is presented as part of a statement of intent. But I
don't have any of my C documentation available at present to
check with. There's also a rule that accesses (reads and
writes) to volatile variables is observable behavior. But (at
least if my memory serves me correctly), it also says that what
constitutes an access is implementation defined. This has two
consequences:
-- The implementation is required to document it. As far as I
can tell, this means that I've never seen a conformant
implementation, since I've yet to be able to find such
documentation. (G++ does have a section §4.10 where it
claims to document "What constitutes an access to an object
that has volatile-qualified type". Regretfully, the couple
of paragraphs in the section manage to not say anything
concrete.)
-- Judging from the generated code, most compilers (well, g++,
Sun CC and the versions of VC++ that I'm using) define an
"access" as something along the lines of "having executed a
load or store instruction in the instruction stream". Given
modern processors, this definition is more or less useless,
and (at least IMHO) definitely violates the intention. (One
would expect "access" to be defined in terms of something
happening on the actual "object", e.g a read or write cycle
inmain memory or something similar.)
See, for example, §8.2 of the Sparc Architecture Manual, in
particular the fifth paragraph, "The value semantics of
operations on I/O locations are not defined by the memory
models, but the constraints on the order in which operations are
performed is the same as it would be if the I/O locations were
real memory", plus the "Compatibility Note" which follows
(non-normative, but explanitory of the above):
Operations to I/O locations are not guaranteed to be
sequentially consistent between themselves, as they are
in SPARC-V8. SPARC-V9 does not distinguish real memory
from I/O locations in terms of ordering. All references,
both to I/O locations and real memory, conform to the
memory model’s order constraints. References to I/O
locations may need to be interspersed with MEMBAR
instructions to guarantee the desired ordering. Loads
following stores to locations with side effects may
return unexpected results due to lookaside into the
processor’s store buffer, which may subsume the memory
transaction. This can be avoided by using a MEMBAR
#LookAside.
This means that it may not be possible to write a device driver
in pure C or C++, and be sure it works. Alternatively, the
hardware may do something to impose Total Store Order on
accesses to the memory mapped IO. This is only a problem with
the more relaxed models. There's even a hint at the end of §8.2
suggesting that hardware implementations treat addresses that
might be memory mapped IO using a sequentially consistent
model. So you really have to check the documentation of your
hardware, and maybe be prepared for the worst.