(6.7.3.6): What constitutes an access to an object that has volatile-qualified type is implementation-defined.
That seems to me like giving 'complete freedom in deciding what constitutes an "access"'. Or almost.
Actually, chapter 4.10 of GCC 4.4 manual documents how is this defined by GCC and explicitly states, that discarding result of read of volatile object does not always cause access to such object.
(5.1.2.3.2): Accessing a volatile object, ..., or calling a function that does any of those operations are all side effects...
(5.1.2.3.3): In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
It does not look like this allows an implementation complete freedom in deciding what constitutes an "access". In fact, I'm pretty certain that 'volatile int i; i;' is required to access i exactly once. (There are plenty of optimizer bugs in this area, but these are bugs.)
(Note that the above has nothing to do with threads and everything with memory-mapped devices. Perhaps you were thinking of threads? These are not part of the C standard, though.)