将指向struct的指针强制转换为指向该结构的唯一成员的指针

时间:2014-11-25 21:47:34

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

考虑以下计划:

#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的指针”类型的prvalue v转换为该类型时   “指向 cv T2的指针”,如果static_cast<cvT2*>(static_cast<cvvoid*>(v))T1都是T2,则结果为T2   标准布局类型(3.9)和T1的对齐要求是   没有比{{1}}

更严格的条款

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

2 个答案:

答案 0 :(得分:5)

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

由于reinterpret_cast<const int*>(values.data())

[class.mem]/19没问题

  

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

关于解除引用,[expr.reinterpret.cast]/7

  

可以将对象指针显式转换为不同类型的对象指针。当对象指针类型的prvalue v转换为对象指针类型“指向cv T的指针”时,结果为static_cast<cv T*>(static_cast<cv void*>(v))

第一个static_cast[conv.ptr]/2

涵盖
  

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

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

  

类型为“指向cv1 void的指针”的prvalue可以转换为类型为“指向cv2 T的指针”的prvalue(...)如果原始指针值表示内存中字节的地址A且A满足T的对齐要求,则结果指针值表示与原始指针值相同的地址,即A。

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


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

您可以做的是放入您的代码

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

因此,如果违反要求,至少你的代码不会编译。

答案 1 :(得分:3)

这是对的。在某些条件下,指向结构的指针可以转换为指向它的第一个成员的指针。这是C的遗留保留,因为这是当时实现完全不继承的原因。

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

  

指向标准布局结构对象的指针,适当地使用转换   a reinterpret_cast,指向其初始成员(或者如果该成员是   比特字段,然后到它所在的单元,反之亦然。 [   注意:因此可能有一个未命名的填充   标准布局结构对象,但必要时不在其开头   实现适当的对齐。 - 结束记录]