memcpy () для члена структуры, приведенного из непрозрачного указателя - PullRequest
1 голос
/ 27 апреля 2020

Допустим, у меня есть 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));
}

Есть ли какое-либо неопределенное поведение в методе A::WriteVal?

Моё предположение было бы НЕТ по следующим причинам:

  1. Переинтерпретировать приведение между uint64_t * и Impl * само по себе нормально, поскольку выравнивание типов pointee одинаково.
  2. Существует только UB, если ptr были быть явно разыменованным, поскольку это нарушило бы строгие правила наложения имен.
  3. memcpy можно безопасно использовать вместо явной разыменования независимо от исходного типа указателя.

Является ли мой рассуждения верны? Если это также считается UB, есть ли хороший способ записи в C ++ непрозрачного типа без недопустимых методов штамповки типов.

Моя цель состоит в том, чтобы аккуратно выполнить операции над непрозрачным значением, которое под капотом представляет структура, о которой пользователи не должны знать детали.

1 Ответ

2 голосов
/ 27 апреля 2020

Ваши рассуждения охватывают неопределенное поведение, вызванное нарушением строгого псевдонима . Использование memcpy действительно является способом сделать типизацию определенным образом.

Хотя есть и другие потенциальные источники неопределенного поведения. В вашем примере кода также должны контролироваться выравнивание и заполнение структуры:

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

Я не вижу других возможных источников неопределенного поведения в вашем примере кода.

...