For instance, the original DEC Alpha (though later versions of the
chip acquired byte load and store via the so-called "BWX" option)
Current MIPS chips, which are found in equipment being made and
sold today (many readers probably have them in their own homes),
still do 32-bit accesses as a minimum.
On such an implementation if the structure did not have any padding the
compiler would not be able to prevent all bytes being a[ccess]ed.
This is true for any implementation of any language; the peripherals
must be designed to be accessible via CPU instructions, so my
struct T {uint8_t read, write, status, forkicks;};
doesn't describe a peripheral on such a platform.
Indeed. But it shows some of the limitations the C Standard has
in setting limitations on implementations: if Standard C required
implementations to do only single-byte load and store for volatile
"uint8_t" accesses, Standard C would be impossible on old DEC Alpha
chips, and on current MIPS chips.
OTOH, it can be a normal memory object (+ consider a "normal" platform
with a volatile bitfield member of a struct). The closest thing to "the
right thing" would be to disable the interrupts while reading/writing.
Will a(ny) compiler do this?
One might. I have not seen any that would, though.
The *intent* of the volatile qualifier is clear enough. It means,
in effect: "Hey Mr Compiler, this object does not behave like
ordinary RAM, so do not assume that anything you put in it stays
there, nor that anything you read from it is still there even
nanoseconds later." What the compiler *does* with that extra bit
of information is up to the implementation. In general, if you
are accessing memory-mapped I/O hardware, using the "volatile"
qualifier is necessary, but sometimes not sufficient.
Memory-mapped I/O is of course not required, and there are systems
that do not have it (systems where "I/O" operations are separate
instructions like "inb" and "outb", for instance; of course no one
would buy a chip with such instructions today

). So in "100%
Standard" C, where you would not even *think* of accessing I/O
hardware

, there are only two situations in which you need
"volatile":
- when working with a sig_atomic_t variable used in a signal
handler, and
- when using setjmp() and longjmp(), where you should qualify
all local variables as "volatile". (You can get away without
it for some local variables, but figuring out which ones can
be somewhat painful, and it is easier and safer to start by
marking them all volatile. Later, when doing optimization, if
profiling shows that some of them are causing performance
problems, you can work out which one(s) are safe.)
Personally, I wish that Standard C did not require "volatile" for
either of these situations (and I think C99 does not for the first
one, having smuggled the "volatile" into sig_atomic_t). But C is
what the Standard says it is, and as C programmers, we must either
go with that, or be very certain *why* we are not doing so. (That
is, you can abandon the Standard any time you like, but you should
*know* that you are doing this, and what you stand both to gain
and to lose in the process.)