1: Don't use bitfields when interacting with registers. The language does not guarantee the behavior you want. Or even the behaviour you think you want.
2: 'volatile' is the contract that we (language users) have hammered out with the compiler industry over the last 20 years; it means "don't get smart with this memory location". Use it.
3: For any SoC of more than trivial complexity, the vendor will supply headers that describe the hardware (register names and addresses, field names, sizes, offsets, enumerations, etc.). You will hate their naming convention. You will hate the way they encode field sizes, offsets, masks, etc. Deal. Because the alternative is you, or your intern, making dozens of mistakes attempting to transcribe from the documentation. Here's a hint - the better vendors auto-generate these headers from the VHDL. They are often right even when the documentation isn't.
4: It's trivial to template most interactions with registers. See, for example: https://github.com/steffanw/laks
5: A little investment in your abstractions, even if the lower levels look a bit more complex or verbose, helps make your application logic easier to follow.
The discussion here about re-ordering is largely moot; if you're having issues with the core re-ordering your transactions, either your core is børked or you haven't mapped your peripheral space correctly. The former is pleasantly rare, the latter common enough that your compiler vendor is going to ask you about it before they give you a bug number.