Normally, it is often problematic to specify qualifiers on individual struct members rather than on the struct object. In this case, since every single member is volatile
, you could as well have done volatile struct bar bar1
, allowing non-volatile
instances of the struct to exist as well.
However, in case a
and b
happens to be memory-mapped CPU registers, there exists no situation where they aren't volatile
so by qualifying the individual members as such, you prevent any other use of them which is a good thing in that particular scenario. There would be no harm of making the struct volatile
as well though.
As for the use of these variables, the only thing that matters is the type used for the "lvalue access" of the member object, which must be done through a correctly qualified type. For example * (int*)&bar->a
would invoke undefined behavior - same thing if you used const
in the declaration.
A compiler can't assume anything about this struct either, it must treat it as volatile
all the way. It would however have been much clearer and self-documenting to the programmer if it was explicitly declared volatile
at every turn. Not declaring something volatile
when the intention is to treat it as volatile
is simply bad programming style. But as far as the C language cares, that's not necessary.
You can easily test this: https://godbolt.org/z/rxqPe8abx
Comment out volatile
and the (x86_64 port) compiler optimizes the writes into a single 32 bit write, which wouldn't be conforming for 2 volatile
variables. With volatile
we do get 2 separate 16 bit writes, which is required by C's rules of program execution ("the abstract machine").