Low-Level Memory

std::memcpy and std::bit_cast

The function std::memcpy copies a section of a sequence of bytes from one position in memory to another. std::bit_cast allows you to convert a value's bit pattern into a different type of the same size. They perform similar tasks but have different implications.

One simple use of std::memcpy just copies one int to another:

#include <iostream>
#include <cstring>

int main()
{
    int a{42};
    int b{};
    // Call this function with the provided arguments
    std::memcpy(&b, &a, sizeof(a));
    std::cout << b << '\n';
}

The object a has the value 42. The call to std::memcpy(&b, &a, sizeof(a)) copies exactly sizeof(a) bytes from a to b. After this, b will contain the value 42, so the program outputs 42. a and b are the same type and are both trivially copyable.

Likewise, it is a function that can be used to view the bytes of a value as a different type, if the sizes of the types are the same and the types are trivially copyable.

#include <iostream>
#include <cstring>

int main()
{
    int x{0x12345678};
    float f{};
    // Pass the object's address to the function
    std::memcpy(&f, &x, sizeof(x));
    std::cout << f << '\n';
}

The integer x has a known bit pattern. The call std::memcpy(&f, &x, sizeof(x)) copies those bits into the float f. The float now contains whatever value those bits represent under the floating point format of the machine.

The original int x remains unchanged.

Because the copy is done via std::memcpy rather than a cast, strict aliasing isn't broken and the program remains well defined.

Since C++20, we have std::bit_cast, which does a similar reinterpretation in a one liner with much stronger guarantees.

#include <iostream>
#include <bit>

int main()
{
    // Use hexadecimal notation for this integer literal
    int x{0x12345678};
    float f{std::bit_cast<float>(x)};
    std::cout << f << '\n';
}

Here, the call to std::bit_cast<float>(x) constructs a float with the same bits as the object representation of the int x. This is stored in f and output. Both types must be the same size and trivially copyable, else the program will not compile. No address is taken and no intermediate buffer is used; the operation is therefore less prone to misuse than a manual memcpy.

Both methods will fail when the sizes of the two types don't match. std::memcpy will just copy the wrong number of bytes if the 3rd argument is wrong, resulting in an incomplete or overrun result, which is almost certainly not what you want. std::bit_cast will not compile if the sizes don't match.

Both functions must not be used to copy types that are not trivially copyable. If the programmer uses memcpy or bit_cast on a type which has a non trivially copyable copy constructor, or a non trivial destructor, the program will have undefined behavior. The compiler may make any assumption that the type will never be moved by a raw bytes copy, and generate code which will not work if that assumption is false.

The function std::memcpy works for any two trivially copyable objects of the same size. It has been around since the beginning of C++. The function std::bit_cast is clearer for the most common form of the aliasing operation: reinterpreting one value as another.

However, it needs C++20 as well as the header <bit>.

Both of these functions don't alter the source object and both result in a value representation taken directly from the source bits.

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.