使用std :: launder获取指向活动联合成员的指针,而不是指向非活动联合成员的指针?

Oli*_*liv 8 c++ pointers undefined-behavior language-lawyer c++17

考虑这个联合:

union A{
  int a;
  struct{
    int b;
    } c;
  };
Run Code Online (Sandbox Code Playgroud)

c并且a不是布局兼容类型,因此无法读取bthrough 的值a

A x;
x.c.b=10;
x.a+x.a; //undefined behaviour (UB)
Run Code Online (Sandbox Code Playgroud)

对于审判1和审判2,请参阅此问题

试用3

现在让我们使用std::launder它似乎不想要的东西:

A x;
x.a=10;
auto p = &x.a;                 //(1)
x.c.b=12;                      //(2)
p = std::launder(p);           //(2')
*p+*p;                         //(3)  UB?
Run Code Online (Sandbox Code Playgroud)

可以std::launder改变什么吗?根据[ptr.launder]

template <class T> constexpr T* launder(T* p) noexcept;

需要p表示内存中一个字节的地址A。在其生存期内且类型相似的对象XT位于地址A处。通过结果可以访问的所有存储字节都是可以访问的p(请参见下文)。

返回T *指向X的类型的值。

备注:只要可以在核心常量表达式中使用其参数的值,就可以在核心常量表达式中使用此函数的调用。通过一个指向对象Y的指针值可以到达一个存储字节,如果该对象位于Y占用的存储空间内,则该对象可以与Y进行指针可互转换,如果Y是数组元素,则可以立即封闭数组对象。如果T是函数类型或cv void,则程序格式错误。

粗体字强调了困扰我的东西。如果p是无效的指针值,那么如何访问任何字节的存储空间?另一方面,这样的阅读std::launder是不可用的。

否则,可能p的在(2)的值是代表存储的区域作为在‘注意’的谈及的指针值[basic.life]

如果不满足这些条件,则可以通过调用std?::?launder([support.dynamic])从表示其存储地址的指针获取指向新对象的指针。

Ben*_*igt 8

注释中明确允许使用它。

basic.life包含以下使std::launder不必要的规则

如果在对象的生存期结束之后并且在重新使用或释放​​该对象占用的存储空间之前,在原始对象占用的存储位置上创建了一个新对象,则指向原始对象的指针,引用原始对象,否则原始对象的名称将自动引用新对象,并且在新对象的生命周期开始后,可以用于操作新对象,如果:

  • 新对象的存储空间正好覆盖了原始对象所占据的存储位置,并且
  • 新对象与原始对象具有相同的类型(忽略顶级cv限定词),并且
  • 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,并且
  • 原始对象和新对象都不是潜在重叠的子对象。

[注:如果不满足这些条件,则可以通过调用从表示其存储地址的指针获取指向新对象的指针std::launder。-?尾注?]

这种“新对象是在原始对象所占的存储位置创建的”案例在此明确适用,因为:

在隐式更改联合的活动成员时创建对象...

满足所有项目符号条件,因为“潜在重叠的子对象”是指基类子对象,而联合成员不是。(在您链接的版本中,该项目符号直接提到了基类子对象。)

但是,即使此解释将改变为反对工会,该注释也特别提到std::launder绕过此限制。

请注意,较早版本的Standard将该子对象排除在此规则之外...但是该注释清楚地表明,它也std::launder将绕过该问题。