【问题标题】:Cast a pointer to struct to a pointer to the only member of that struct将指向结构的指针转换为指向该结构唯一成员的指针
【发布时间】:2015-01-24 02:02:34
【问题描述】:

考虑以下程序:

#include <algorithm>
#include <iostream>
#include <vector>

struct foo {
    foo(int value)
    : value_(value)
    {
        // perform range checks
    }

    int value() const {
        return value_;
    }

private:
    int value_;
};

int main() {
    std::vector<foo> values{0, 1, 2, 3, 4, 5};

    std::for_each(std::begin(values), std::end(values), 
                  [](foo& f){ std::cout << f.value(); });

    std::cout << std::endl;

    std::for_each(reinterpret_cast<const int*>(values.data()),
                  reinterpret_cast<const int*>(values.data()) + values.size(),
                  [](int i){ std::cout << i; });
}

使用 Apple LLVM 6.0 版 (clang-600.0.54)(基于 LLVM 3.5svn)编译后,它会产生以下输出(这正是我想要的):

012345
012345

第一次迭代很简单。然而,第二次迭代不是通过迭代器执行的,而是通过指向已转换为const int* 的底层存储的指针。

我的问题是:该代码合法吗?

我的直觉是。根据 C++11 标准的§5.2.10/7(最终工作草案):

当“指向T1”的指针类型的纯右值v被转换为类型 “指向cvT2的指针”,如果T1T2都是static_cast&lt;cvT2*&gt;(static_cast&lt;cvvoid*&gt;(v)) 标准布局类型 (3.9) 和 T2 的对齐要求是 不比T1更严格

如果我解释正确,那么上面的代码应该是正确的,对吧?如果没有,可以让它工作吗?

【问题讨论】:

  • 有一件事是肯定的:这不违反严格的别名(因为结构有一个double 对象作为它们的初始成员)。这让我想起了std::complex&lt;T&gt; 的要求,即它的地址必须可转换为两个Ts 数组的第一个元素(即T *)。所以我想在这种情况下不允许尾随填充,你会很安全。
  • 谢谢,我想这很可能是一个答案。
  • :) 我不这么认为——只是因为我没有基于标准的证据来支持它……只是一个例子。这很好,但不能保证:)
  • std::complex&lt;T&gt; 有一个特殊条款说它必须工作

标签: c++ pointers c++11 struct strict-aliasing


【解决方案1】:

(在我的回答中,我使用 C++14 标准草案(N4140),在相关引用方面与 C++11 有点不同)

reinterpret_cast&lt;const int*&gt;(values.data()) 很好,因为[class.mem]/19

如果标准布局类对象有任何非静态数据成员,则其地址与其第一个非静态数据成员的地址相同。 (...) [注意:因此,标准布局结构对象中可能存在未命名的填充,但不是在其开头,这是实现适当对齐所必需的。 ——尾注]

关于取消引用,[expr.reinterpret.cast]/7:

对象指针可以显式转换为不同类型的对象指针。当对象指针类型的prvalue v转换为对象指针类型“pointer to cv T”时,结果为static_cast&lt;cv T*&gt;(static_cast&lt;cv void*&gt;(v))

第一个static_cast[conv.ptr]/2覆盖:

类型“pointer to cv T”的纯右值,其中T是对象类型,可以转换为“pointer”类型的纯右值 简历void”。将指向对象类型的指针的非空指针值转换为“指向对象的指针”的结果 cvvoid”表示与原始指针值相同字节在内存中的地址。

第二个static_cast - [expr.static.cast]/13

“指向 cv1 void 的指针”类型的纯右值可以转换为“指向 cv2 T 的指针”类型的纯右值,(...) 如果原始指针值表示内存中一个字节的地址 A 并且 A 满足T的对齐要求,那么得到的指针值代表的地址与原来的指针值相同,即A。

[class.mem]/19 满足对齐要求,因此演员表工作正常。


但问题是除了上述对std::complex 的要求外,似乎无法保证sizeof(foo) == sizeof(int)。可以将[class.mem]/19 中关于未命名填充的注释解释为仅在需要对齐时才允许填充,因此在您的情况下不能有任何填充,但我认为该注释在这方面过于模糊。

你能做的就是把你的代码放进去

static_assert(sizeof(foo) == sizeof(int), "");
// this may be paranoic but won't hurt
static_assert(alignof(foo) == alignof(int), "");

因此,如果违反要求,至少您的代码将无法编译。

【讨论】:

    【解决方案2】:

    没错。在此处满足的某些条件下,可以将指向结构的指针转换为指向其第一个成员的指针。这是 C 的遗留物,因为这就是当时完全非继承的实现方式。

    这是在 C++11 §9.2/20 [class.mem] 中指定的:

    一个指向标准布局结构对象的指针,使用适当的转换 reinterpret_cast,指向它的初始成员(或者如果该成员是 一个位域,然后到它所在的单元),反之亦然。 [ 注意:因此在一个 标准布局结构对象,但不是在其开头,根据需要 以达到适当的对齐。 — 尾注 ]

    【讨论】:

    • 这就是我所期望的。事实上,它的用例是与 C API 兼容。
    • 注意提供支持您所说的标准文本吗?
    • @Puppy:经过一番挖掘,我找到了相关的标准报价并将其添加到您的答案中。我希望你没问题。
    • @BjörnPollex 我只看到一个问题 - 是否可以保证sizeof(foo) == sizeof(int)
    • 注意:这个文本在 C++14 中变得更加模糊,它现在说“如果一个标准布局类对象有任何非静态数据成员,它的地址与它的第一个非静态数据成员 [...]"
    猜你喜欢
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-29
    • 1970-01-01
    • 2020-10-21
    相关资源
    最近更新 更多