  The C/C++ memory model provides an interface and execution model for
programmers of concurrent (shared-variable) code. It provides a range of
mechanisms that abstract from underlying hardware memory models -- that govern
how multicore architectures handle concurrent accesses to main memory -- as
well as abstracting from compiler transformations. The C standard describes the
memory model in terms of cross-thread relationships between events, and has
been influenced by several research works that are similarly based. In this
paper we provide a thread-local definition of the fundamental principles of the
C memory model, which, for concise concurrent code, serves as a basis for
relatively straightforward reasoning about the effects of the C ordering
mechanisms. We argue that this definition is more practical from a programming
perspective and is amenable to analysis by already established techniques for
concurrent code. The key aspect is that the memory model definition is separate
to other considerations of a rich programming language such as C, in
particular, expression evaluation and optimisations, though we show how to
reason about those considerations in the presence of C concurrency. A major
simplification of our framework compared to the description in the C standard
and related work in the literature is separating out considerations around the
"lack of multicopy atomicity", a concept that is in any case irrelevant to
developers of code for x86, Arm, RISC-V or SPARC architectures. We show how the
framework is convenient for reasoning about well-structured code, and for
formally addressing unintuitive behaviours such as "out-of-thin-air" writes.
