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

Cast a pointer to struct to a pointer to the only member of that struct

本文关键字:指针 结构 唯一 成员 转换      更新时间:2023-10-16

请考虑以下程序:

#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)),如果 T1T2 都是 标准布局类型 (3.9) 和T2的对齐要求是 不比T1

严格

如果我正确解释了这一点,那么上面的代码应该是正确的,对吧?如果没有,它能起作用吗?

(在我的回答中,我使用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 到简历void"。将指向对象类型的指针的非 null 指针值转换为"指针指向"的结果 cv void " 表示内存中与原始指针值相同的字节的地址。

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

类型为"指向 cv1 void 的指针"的 prvalue 可以转换为"指向 cv2 T 的指针"类型的 prvalue,(...)如果原始指针值表示内存中某个字节的地址 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), "");

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

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

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

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