Both the qualifiers const and volatile were new in ANSI C (which became ISO C89). In fact I believe the idea of type qualifiers in C is imported from (pre-standard) C++, thus it's Bjarne's fault.
The general idea (if we force the optimiser to emit the memory access then we can abuse that to do MMIO) is older than ANSI C and as I understand it begins when peephole optimisers begin to first make the "obvious" trick not work in C compilers, but I don't know when it became the volatile qualifier.
As in C++ the correct fix is to use dedicated intrinsics. JF Bastien wrote up C++ intrinsics for this as a template, obviously the C intrinsics would not be a template, but the general idea applies. In reality your hardware does not implement crazy nonsense like a 196-bit unaligned non-tearing memory fetch, so you don't need customisable intrinsics.
e.g. maybe C gets __volatile_load_64(ptr) and that's 64-bit aligned fetch from the address in ptr, there would be a handful you actually need for 8-bit, 16-bit, 32-bit, 64-bit, maybe 128-bit, loads plus stores and perhaps implementations are asked to offer any special cases for their platform, I can imagine unaligned 32-bit is plausible on x86 for example, maybe some DSP has 24-bit, that sort of thing.
The idea is the intrinsic emits the same CPU instructions which are what happens for volatile access today, but as intrinsics they don't give the false impression you can do other stuff, this isn't really memory even though the CPU instructions are memory access instructions.