1

假设我有一个 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. 重新解释 auint64_t *Impl *它自己之间的转换是可以的,因为指针类型的对齐方式是相同的。
  2. 如果ptr要明确取消引用,则只有 UB,因为这会破坏严格的别名规则。
  3. memcpy无论指针的原始类型如何,都可以安全地用于替代显式取消引用。

我的推理正确吗?如果这也被认为是 UB,那么是否有一种很好的 C++ 方式来写入不透明类型而没有非法类型双关语方法。

我的目标是对一个不透明的值进行干净的操作,在底层,它代表一个用户不应该知道细节的结构。

4

1 回答 1

2

您的推理涵盖了由严格的别名违规引起的未定义行为。Usingmemcpy确实是一种以定义的方式进行类型双关的方法。

但是,还有其他潜在的未定义行为来源。在您的示例代码中,还应控制结构的对齐和填充:

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

在您的示例代码中,我没有看到任何其他可能的未定义行为来源。

于 2020-04-27T09:46:18.993 回答