Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe usage of `reinterpret_cast`

Tags:

c++

If I have an aggregate of integral types and I want to create a instance of it with random, is reinterpret_cast being used here safe?

template <typename T>
auto random() -> T {
  static auto random_device = std::random_device{};
  static auto generator     = std::mt19937(random_device());
  static auto distribution  = std::uniform_int_distribution<std::uint8_t>{};

  auto bytes = std::array<std::uint8_t, sizeof(T)>{};

  for (auto i = std::size_t{0}; i < bytes.size(); ++i) {
    bytes[i] = distribution(generator);
  }

  return *reinterpret_cast<T *>(bytes.data());
}
like image 642
Uy Hà Avatar asked Oct 20 '25 03:10

Uy Hà


2 Answers

Firstly this will not compile in the general case. You cannot reinterpret_castinto an arbitrary type. IfT` is not a pointer type or an integral type this will not compile.

To fix this you probably want to return

return *reinterpret_cast<T*>(bytes.data());

But do not do that either. If T is not ((un)signed) char or std::byte this is undefined behavior.

The correct way to implement such a function is to first make sure the target type T satisfies the criteria to do this. I will over-restrict it by requiring that it is trivial:

decltype(auto) get_mt19937() {
    static auto random_device = std::random_device{};
    static auto generator     = std::mt19937(random_device());
    return generator;
}

template <typename T>
auto random() -> T {

    static_assert(std::is_trivial_v<T>, "T must be trivial");

    // This is bad. It will instantiate a new mt19937 engine for each type `T`.
    //static auto random_device = std::random_device{};
    //static auto generator     = std::mt19937(random_device());
    
    auto& generator = get_mt19937();

    // no need to static
    auto distribution  = std::uniform_int_distribution<unsigned char>{};

    auto return_val = T{};
    // reinterpret_cast'ing to std::uint8_t* might not be legal
    auto* bytes = reinterpret_cast<unsigned char*>(&return_val);

    for (auto i = std::size_t{0}; i < sizeof(T); ++i) {
        bytes[i] = distribution(generator);
    }


    return return_val;
}
like image 105
Mestkon Avatar answered Oct 23 '25 04:10

Mestkon


For a generic type T, this may not be not safe. According to the c++ reference, a reinterpret_cast is allowed if:

  1. Any object pointer type T1* can be converted to another object pointer type cv T2*. This is exactly equivalent to static_cast<cv T2*>(static_cast<cv void*>(expression)) (which implies that if T2's alignment requirement is not stricter than T1's, the value of the pointer does not change and conversion of the resulting pointer back to its original type yields the original value). In any case, the resulting pointer may only be dereferenced safely if allowed by the type aliasing rules (see below).

So, you can do the cast from *std::uint8_t to *T. But the problem is, when can you dereference the resulting pointer to T? Not for every type T is this allowed. The same page says that you can dereference to a AliasedType T if:

AliasedType is std::byte, (since C++17) char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.

So, if you restrict the use of your routine to the std::byte, char, or unsigned char (as mentioned above in the second quote), yes, you can then safely dereference the pointer in the return statement and your function should be safe.

But, you should rather consider to use std::byte to store a generic raw-memory buffer.

like image 25
francesco Avatar answered Oct 23 '25 04:10

francesco



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!