Why does std::memcpy (as an alternative to type-punning) not cause undefined behaviour?

Viewed 506

While searching for a way to combine sizeof(double) chars to a double, I read in several posts, that using std::memcpy was the recommended way:

char bytes[sizeof(double)];
// fill array
double d;
std::memcpy(&d, bytes, sizeof(double));

However, I wonder why further usage of d can be defined behavior.

If it was not a double, but a complex class object, accessing it surely would not be defined either, would it? So, why should it be the case for a double.

Edit: To make my problem clear, I wanna specify my goal: I would like to find a way to combine several chars to a double and further use this double, without causing undefined behavior. I do not expect the value of the double to be specified. I deem this impossible anyway, since the standard does not even say anything about the size, not to mention bit layout of double. However, I demand d to have some valid (i. e. 'accessible') double-value.

3 Answers

Type punning is forbidden because the idea of it makes a mockery of the C++ object model. A piece of memory stores an object, and if you start accessing it as if it stored some other object, then what does that even mean? If you can just willy-nilly read from memory as an int, write to it as a float, and read from it later as a short, then what does it even mean to have an object exist?

Copying bytes between trivially copyable objects is just another way of setting that object's value. Indeed, that is what it logically means for an object to be "trivially copyable": that the meaning of that object is solely defined by the sequence of bytes that make up its object representation (this is not the case for complex objects). But the sanctity of what memory belongs to which objects is preserved. There is no "punning"; there is just copying data around.

Why does type-punning using std::memcpy not cause undefined behaviour?

Beause the language says so (latest draft):

[basic.types]

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]). If the content of that array is copied back into the object, the object shall subsequently hold its original value.

Note however the condition on that rule. Your code can potentially have undefined behaviour, but not (unless some other rule says so) in case the copied value was originally copied from another double, or in practice, if the value could have been copied from a double.

If it was not a double, but a complex class object, accessing it surely would not be defined either, would it?

Depends on what you mean by complexity. The conditions where this applies are in the quoted rule.

There is a special exception in the standard for memcpy to/from a buffer of bytes, because certain operations would be impossible if there weren't a well defined way of doing that.

You can definitely get undefined behavior if you copy from one type, to bytes, then to another type.

Related