memcpy() on struct member casted from an opque pointer

164 Views Asked by At

Let's say I have an API:

// api.h - Others can #include this header
#include <cstdint>

class A {
 public:
  // Write data into an opaque type.
  // The user shouldn't directly access the underlying bits of this value.
  // They should use this method instead.
  void WriteVal(uint32_t data);

 private:
  uint64_t opaque_value_;
};
// api.cpp
#include <cstdlib>
#include "api.h"

namespace {

// This is the underlying struct that the opaque value represents.
struct alignas(8) Impl {
  uint32_t x;
  uint32_t y;
};

}  // namespace

void A::WriteVal(uint32_t data) {
  uint64_t *opaque_ptr = &opaque_value_;
  Impl *ptr = reinterpret_cast<Impl *>(opaque_ptr);
  memcpy(&ptr->y, &data, sizeof(data));
}

Is there any undefined behavior in the A::WriteVal method?

My guess would be NO for these reasons:

  1. Reinterpret casting between a uint64_t * and Impl * on it's own is ok since the alignment of the pointee types is the same.
  2. There is only UB if ptr were to be dereferenced explicitely since that would break strict aliasing rules.
  3. memcpy can be safely used in substitution of explicit dereferencing regardless of the original type of the pointer.

Is my reasoning correct? If this is also considered UB, is there a nice C++ way of writing to an opaque type without illegal type punning methods.

My goal is to cleanly perform operations on an opaque value that, under the hood, represents a struct that users shouldn't know the details about.

1

There are 1 best solutions below

0
Sander De Dycker On

Your reasoning covers undefined behavior caused by a strict aliasing violation. Using memcpy is indeed a way to do type punning in a defined way.

There are other potential sources of undefined behavior though. In your example code, the alignment and padding of the struct should be controlled as well :

struct alignas(uint64_t) Impl {
  uint32_t x;
  uint32_t y;
};
static_assert(sizeof(Impl) == sizeof(uint64_t), "sizeof(Impl) not valid");

I don't see any other possible sources of undefined behavior in your example code.