Is std::memcpy between different trivially copyable types undefined behavior?

  • A+

I've been using std::memcpy to circumvent strict aliasing for a long time.

For example, inspecting a float, like this:

float f = ...; uint32_t i; static_assert(sizeof(f)==sizeof(i)); std::memcpy(&i, &f, sizeof(i)); // use i to extract f's sign, exponent & significand 

However, this time, I've checked the standard, I haven't found anything that validates this. All I found is this:

For any object (other than a potentially-overlapping subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes ([intro.memory]) making up the object can be copied into an array of char, unsigned char, or std​::​byte ([cstddef.syn]).40 If the content of that array is copied back into the object, the object shall subsequently hold its original value. [ Example:

#define N sizeof(T) char buf[N]; T obj;                          // obj initialized to its original value std::memcpy(buf, &obj, N);      // between these two calls to std​::​memcpy, obj might be modified std::memcpy(&obj, buf, N);      // at this point, each subobject of obj of scalar type holds its original value 

— end example ]

and this:

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a potentially-overlapping subobject, if the underlying bytes ([intro.memory]) making up obj1 are copied into obj2,41 obj2 shall subsequently hold the same value as obj1. [ Example:

T* t1p; T* t2p; // provided that t2p points to an initialized object ... std::memcpy(t1p, t2p, sizeof(T)); // at this point, every subobject of trivially copyable type in *t1p contains // the same value as the corresponding subobject in *t2p 

— end example ]

So, std::memcpying a float to char[] is allowed, and std::memcpying between the same trivial types is allowed too.

Is my first example (and the linked answer) well defined? Or the correct way to inspect a float is to std::memcpy it into a unsigned char[] buffer, and using shifts and ors to build a uint32_t from it?


Is my first example (and the linked answer) well defined?

The behaviour isn't undefined (unless the target type has trap representarions that aren't shared by the source type), but the resulting value of the integer is implementation defined. Standard makes no guarantees about how floating point numbers are represented, so there is no way to extract mantissa etc from the integer in portable way - that said, limiting yourself to IEEE 754 using systems doesn't limit you much these days.

Problems for portability:

  • IEEE 754 is not guaranteed by C++
  • Byte endianness of float is not guaranteed to match integer endianness.
  • Systems with trap representarions.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: