你能通过 char* 访问任何对象的对象表示吗?

JMC*_*JMC 10 c++ strict-aliasing language-lawyer

我偶然发现了一个reddit 线程,用户在其中发现了 C++ 标准的一个有趣细节。该线程没有产生太多建设性的讨论,因此我将在这里复述我对问题的理解:

  • OP 希望以memcpy符合标准的方式重新实现
  • 他们试图通过 using 来做到reinterpret_cast<char*>(&foo)这一点,这是严格别名限制的允许例外,其中允许重新解释 aschar访问对象的“对象表示”。
  • [expr.reinterpret.cast]说这样做会导致static_­cast<cv T*>(static_­cast<cv void*>(v)),所以reinterpret_cast在这种情况下等价于 static_cast'ing first tovoid *然后 to char *
  • [expr.static.cast]结合[basic.compound]

“指向 cv1 void 的指针”类型的纯右值可以转换为“指向 cv2 T 的指针”类型的纯右值,其中 T 是对象类型,而 cv2 与 cv1 具有相同的 cv 限定,或比 cv1 更高的 cv 限定。[...]如果原始指针值指向对象 a,并且存在 T 类型的对象 b(忽略 cv 限定)与 a 的指针可相互转换,则结果是指向 b 的指针。[...] [强调我的]

现在考虑以下联合类:

union Foo{
    char c;
    int i;
};
// the OP has used union, but iiuc,
// it can also be a struct for the problem to arise.
Run Code Online (Sandbox Code Playgroud)

从而OP已经到该重新解释一个结论Foo*作为char*在这种情况下产生一个指针指向联合的第一个字符部件(或它的对象表示),而不是向联合本身的对象表示,即,其指向仅与会员。虽然表面上看起来是一样的,并且对应于相同的内存地址,但标准似乎区分了指针的“值”与其对应的地址,因为在抽象的 C++ 机器上,指针属于某个对象只要。将它增加到该对象之外(与数组的 end() 相比)是未定义的行为。

因此,OP 认为,如果标准强制将char*与对象的第一个成员关联,而不是与整个联合对象的对象表示相关联,则在一次增量后取消引用它是 UB,这允许编译器进行优化,就好像结果不可能一样char*永远访问 int 成员的以下字节。这意味着不可能合法地访问与char成员指针可互转换的类对象的完整对象表示。

同样的,如果我理解正确,如果“联合”被简单地替换为“结构”,但我从原始线程中获取了这个例子。

你怎么认为?这是标准缺陷吗?这是一种误解吗?

JMC*_*JMC 5

这个视频@KonradRudolph 在评论(现在是聊天)中链接的

在 40 分钟左右,ISO C++ 委员会成员 Timur Doumler 讨论了访问字节表示的可能性。总结是,任何访问字节表示的尝试,除了memcpyUB 之外,任何访问字节表示的尝试。如果不使用 UB,OP 中的情况就不会出现,因为使用指向对象(如数组)的指针或对其进行任何指针算术的行为本身就是 UB,因为这些操作仅在处理时才明确定义就抽象机而言,实际的数组对象。

此外,虽然将指针重新解释为 achar*本身并不违反别名规则,但从技术上讲并不能保证结果char*将指向对象的第一个字节。

访问字节表示的唯一合法方法是将memcpy对象转换为 char 数组。这意味着重新实现memcpy是不可能的。

Timur Doumler 还将此描述为措辞缺陷,有望在 C++23 中修复,并提出了一篇论文,提出了对此的修复方案。

  • 请注意,相关论文是[P1839](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1839r2.pdf)。它已[被 EWG *特别*批准为 DR](https://github.com/cplusplus/papers/issues/592)。 (2认同)