Why?
A volatile declaration may be used to describe an object
corresponding to a memory-mapped input/output port or an object
accessed by an asynchronously interrupting function. Actions on
objects so declared shall not be ‘‘optimized out’’ by an
implementation or reordered except as permitted by the rules for
evaluating expressions.
One problem is that "What constitutes an access to an object that
has volatile-qualified type is implementation-defined" (6.7.3p6). It
is often the case that a magical memory address performs its magic
only if accessed by the right kind of instruction: For example, an
"I/O register" at location 0xFF001010 might respond to a four-byte
load or store at that address but not even notice a one-byte access
to location 0xFF001012. Since there's no way to get C-the-language
to generate any particular hardware instruction, there's no way to be
sure C-the-language will use the one you desire.
(This is not a theoretical matter, by the way. I once asked a lead
kernel engineer for a large computer vendor why none of their device
drivers were written in C. Her answer amounted to essentially this
issue: The special locations in the hardware worked only when accessed
as a unit, not when accessed byte-by-byte, and there was no reliable way
to ensure that C would use the former -- *especially* if somebody chose
to compile with a "tolerate misaligned pointers" switch enabled...)
On a given implementation there may be ways to ensure that C uses
the desired instructions -- it could be as simple as reading the
implementation's documents. But you can't just shout `volatile' and
expect that all problems are solved, nor should you assume that the
solution for one machine will behave as desired on another.