unless uint8_t happens to be equivalent to char, and it doesn't need to be.
It just happens to be equivalent to unsigned char on all architectures that GCC, Clang, et al. currently support. Which is the reason it is so often used as
equivalent to unsigned char, the char type having special provisions regarding storage representation in the C standard.
I did know what "volatile" does but I have seen that construct used all over the place where there is no realistic/meaningful possibility of the source memory being modified by another process.
Well, it
is possible for the contents of the Flash memory to be changed in an interrupt context, or even due to a hardware fault or a cosmic ray (which is just radiation not absorbed by the atmosphere).
If code wants to be bulletproof, i.e. shield even against the unrealistic/nonsensical situation where the contents indeed are modified concurrently, it uses the
(*(volatile type*)pointer) idiom.
Just because you and I do not see a realistic/meaningful possibility of the memory being modified in unseen ways, does not mean there is none such.

Besides, it costs nothing. It is like the way I like to use
static inline for accessor functions, and
static for local functions, even though I know perfectly well the two are exactly equivalent:
static inline is exactly as likely to be inlined or not as plain
static is. In some cases, it is used as a "cognitive load reducer" for those reading the code.
I can imagine being a vendor library developer, and sprinkling
volatile even in places where not strictly required (because the compiler will have no ancillary knowledge to avoid the access anyway), just to avoid questions like
"why isn't there a volatile here? I think it is causing a bug in my code" because they do not understand the true meaning of the
volatile keyword.
I thought the 'volatile' part of the declaration prevented the compiler from optimizing away writes to memory/registers without subsequent reads.
Technically,
Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.
In for example C11 footnote 111, regarding assignments, the standard says
"The implementation is permitted to read the object to determine the value but is not required to, even when the object has volatile-qualified type."So, thinking that
volatile forces the compiler to do the access is not actually correct; it only forces the compiler to generate code that
behaves strictly according to the rules of the C standard abstract machine.
It is only on current hardware architectures, including all architectures supported by GCC, Clang, Intel Compiler Collection, etc., that the only meaningful way to ensure the rules of the C standard abstract machine are followed, is to ensure that
*(volatile *type)pointer;and
expression = *(volatile *type)pointer;generates code that explicitly loads a value of type
type from
pointer, and that
*(volatile *type)pointer = expression;generates code that explicitly stores the value of the expression at
pointer.
Neither is guaranteed by the C standard. Both are just
practical results on how the C abstract machine can be implemented in current architectures.
If one had followed e.g. LKML in the last decade or two, one would remember several threads on how speculative execution and compiler optimizations affect this. Currently, there are several details where all the above compilers agree and produce effectively the same code, even though the C standard says (or can be interpreted as saying that) the Behaviour is Undefined; just because the compiler users managed to convince the compiler developers that there was a single sane useful use case, and that practical use case overrides any committee-sitters opinions.
This is exactly why I do value the C standard, but believe the practical reality overrides the theory outlined by the standard. (Indeed, in the past, up to C99, the standard only codified existing behaviour agreed upon by multiple compilers; it was only in C11 that we got Annex K and "new stuff" not implemented by any compiler, because of commercial interests by a single company.)
I do not mean to imply in any way that understanding the C standard would not be useful, because it definitely is, and I believe is quite important for anyone writing any kind of portable code, or code compiled with anything except a specific version of a specific compiler. I just do not think it is the last word on anything: the last word is the actual tools we use, the practical real world. I call those who do believe the standard is or should be the last word language-lawyers, and it is an unfair and derisive term, but as I see it, C is and has to be a practical tool and not a theoretical one, because it is still the closest thing we have to a good embedded and systems programming language.