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

Related searches

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/from 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?


Note: looking at std::memcpy's guarantees may not answer this question. As far as I know, I could replace std::memcpy with a simple byte-copy loop, and the question will be the same.

The standard may fail to say properly that this is allowed, but it's almost certainly supposed to be, and to the best of my knowledge, all implementations will treat this as defined behaviour.

In order to facilitate the copying into an actual char[N] object, the bytes making up the f object can be accessed as if they were a char[N]. This part, I believe, is not in dispute.

Bytes from a char[N] that represent a uint32_t value may be copied into an uint32_t object. This part, I believe, is also not in dispute.

Equally undisputed, I believe, is that e.g. fwrite may have written the bytes in one run of the program, and fread may have read them back in another run, or even another program entirely.

Because of that last part, I believe it does not matter where the bytes came from, as long as they form a valid representation of some uint32_t object. You could have cycled through all float values, using memcmp on each until you got the representation you wanted, that you knew would be identical to that of the uint32_t value you're interpreting it as. You could even have done that in another program, a program that the compiler has never seen. That would have been valid.

If from the implementation's perspective, your code is indistinguishable from unambiguously valid code, your code must be seen as valid.

Why would the behavior of std::memcpy be undefined for objects , 10 Answers. Why would the behavior of std::memcpy itself be undefined when used with non-TriviallyCopyable objects? It's not! However, once you copy the underlying bytes of one object of a non-trivially copyable type into another object of that type, the target object is not alive. Why would the behavior of std::memcpy itself be undefined when used with non-TriviallyCopyable objects? It's not! However, once you copy the underlying bytes of one object of a non-trivially copyable type into another object of that type, the target object is not alive . We destroyed it by reusing its storage, and haven't revitalized it by a

std::memcpy, void* memcpy( void* dest, const void* src, std::size_t count ); If either dest or src is an invalid or null pointer, the behavior is undefined, even if count is zero. or not TriviallyCopyable, the behavior of memcpy is not specified and as values of two different types, std::memcpy may be used to convert the  In the same draft, you also find the following text, directly following the text you quoted: 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 base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1.

Your example is well-defined and does not break strict aliasing. std::memcpy clearly states:

Copies count bytes from the object pointed to by src to the object pointed to by dest. Both objects are reinterpreted as arrays of unsigned char.

The standard allows aliasing any type through a (signed/unsigned) char* or std::byte and thus your example doesn't exhibit UB. If the resulting integer is of any value is another question though.


use i to extract f's sign, exponent & significand

This however, is not guaranteed by the standard as the value of a float is implementation-defined (in the case of IEEE 754 it will work though).

std::is_trivially_copyable, For any other type, value is false. The behavior is undefined if std::​remove_all_extents_t<T> is an incomplete type and not (possibly cv-qualified) void. Objects of trivially-copyable types are the only C++ objects that may be safely of obj1 can be copied (e.g. by means of std::memcpy or std::memmove)  Is this failing test that adds zero to a null pointer undefined behaviour, a compiler bug, or something else? reinterpret_cast bug or UB? valgrind shows memory leak in std::make_unique ; Is this C++ member initialization behavior well defined? Is std::memcpy between different trivially copyable types undefined behavior?

Talk:cpp/string/byte/memcpy, An object of trivially copyable or standard-layout type shall occupy contiguous bytes that are incompatible, I'd guess that the behaviour of memcpy is undefined. values of two different types, std::memcpy may be used to convert the values. Its intent is to define behavior that was previously not defined, in a way that most current implementations already work. No existing valid code is made invalid or changes its behavior. 4. Design Decisions 4.1. Trivially-copyable Types. With the current standard, it should be possible to extend offsetof to

modern way to go for converting a value to bytes and back, Hello guys, yesterday a new coworker of mine asked me about converting different types to their Being trivially copyable essentially means that copying the byte representation will Finally, bit_cast uses memcpy instead of std::copy . through a glvalue that is not one of the allowed types, resulting in undefined behavior. int * in = reinterpret_cast < int *>(buffer); // Defined behaviour because alignment is ok. Correct. But probably not in the sense you'd expect. [expr.static.cast] A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.

P0545R0, Sections 4.1 and 4.2, described that trivially-copyable types are already de-facto required to be compatible with offsetof by the current standard. Presence of non-trivial copy constructors and assignment operators also pose no difference to the object data layout in the current implementations.

Comments
  • As long as they have the same size there shouldn't be a problem. However, if you just need to interpret f as uint32_t you may just write (uint32_t&)f. It will interpret the memory location of the float as if it was uint32_t.
  • @NO_NAME My experiment shows that your suggestion violates the strict aliasing rules. coliru.stacked-crooked.com/a/bb54317049f5c8fc
  • Related: stackoverflow.com/questions/3275353/c-aliasing-rules-and-memcpy
  • Related: stackoverflow.com/questions/17789928/…
  • @NO_NAME It still violates the strict aliasing rules. Valid syntax does not imply valid operation. Just like the English sentence "Colorless green ideas sleep furiously" is grammatically correct but meaningless.
  • Separating the single steps involved and qualifying each one as undisputed is making things clear.
  • What the OP observed is interesting though: While the standard in 6.9.2 explicitly permits copying bytes out of a a trivially copyable object it lacks (or appears to lack -- I just looked at all occurrences of memcpy in n4659) an explicit rule allowing copying bytes into such an object. It is probably considered self-understood; the example in 6.9.2 itself copies the bytes back, after all.
  • @PeterA.Schneider Right. There is "If the content of that array is copied back into the object, the object shall subsequently hold its original value." which grants permission to copy back into a trivially copyable object, but general permission to copy into (rather than back into) a trivially copyable object is never explicitly given in the standard, it can only be inferred. That's the gist of my answer.
  • Good reasoning! I've got a question though. A float -> char[] copy is OK. A char[] -> uint32_t is OK too. But, is a direct float -> uint32_t OK too?
  • @geza It's iffy, but I'd say that since treating the bytes in that float as a char[] is allowed, when you do a direct float -> uint32_t, in a sense, you are copying from a char[] to a uint32_t.
  • @StoryTeller as far as I know, unsigned char is the only type guaranteed to not have traps.