In the following example the value-representation of an uint32_t is copied to an uint8_t array. This is done by std::memcpy. As I understand the C++ standard this is totally legal: we are accessing an object of type T via T* casted to a unsigned char*. No aliasing problem, no alignment problem.
The other way round is less obvious. We are accessing the object representation of T via unsigned char*, which is legal. But does the term accessing include changing?
Of course there are no aliasing and no aligment problems. There are however problems if the values in buffer s origin from a foreign source: we must ensure the right endianness and omit trap representations. The right endianness can be checked, so that can be solved.
But what about trap representations? How can we avoid that? Or do uint-types not have trap representations as opposed to say double?
I know that another (more compliant?) way would be shifting the uint8_t values into the uint_t object. We still have to obey the endianness but this should safely omit trap representations.
But shifts of large types on small µC (8-Bit) can be quite expensive!
The next question is if the second try (see below in the code) is equivalent to the memcpy approach with respect to legality and functionality? Well, it looks like the memcpy version is more optimizer friendly.
#include <cstdint>
#include <cstring>
#include <cassert>
typedef uint32_t utype;
constexpr utype value = 0x01020304;
int main() {
utype a{value};
utype b{0};
uint8_t s[sizeof(utype)]{};
// first
std::memcpy(s, &a, sizeof(utype));
assert(s[0] == (value & 0xff));
std::memcpy(&b, s, sizeof(utype));
assert(b == value);
// second
const uint8_t* ap = reinterpret_cast<const uint8_t*>(&a);
s[0] = ap[0]; // explicitly legal in C++
s[1] = ap[1];
s[2] = ap[2];
s[3] = ap[3];
assert(s[0] == (value & 0xff));
uint8_t* bp = reinterpret_cast<uint8_t*>(&b);
bp[0] = s[0]; // same as memcpy or ist this UB ?
bp[1] = s[1];
bp[2] = s[2];
bp[3] = s[3];
assert(b == value);
}