> If so, why is access to regular SRAM not volatile?
The reasons for using volatile will depend on your program usage of the memory and not the memory itself. If you're only accessing memory within a main loop, and interrupts don't touch it, there is no need to use volatile, regardless of the memory type. If memory can be changed within interrupts, and you want the main loop to re-read that register on every access, you need to use volatile to prevent the compiler from optimizing it away.
> I also still don't know why ST introduced that __IO thing (besides adding volatile)? The definition mentions "read/write permissions", but no one is checking this?!
There's nothing to enforce with __IO. You can't do anything except read or write to the register.
Registers defined as __I are enforced to be read-only using const. Not sure offhand why it's different in C++ than in C here.
Registers defined as __O can't be enforced to be write-only since there's no such restriction in C.
#ifdef __cplusplus
#define __I volatile /*!< Defines 'read only' permissions */
#else
#define __I volatile const /*!< Defines 'read only' permissions */
#endif
#define __O volatile /*!< Defines 'write only' permissions */
#define __IO volatile /*!< Defines 'read / write' permissions */