使用std::launder从指向非活动联合成员的指针获取指向活动联合成员的指针?

8
考虑这个联合:
union A{
  int a;
  struct{
    int b;
    } c;
  };

ca不是布局兼容类型,因此无法通过a读取b的值:

A x;
x.c.b=10;
x.a+x.a; //undefined behaviour (UB)

请参见this question中的试验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?

你认为 std::launder 会改变什么吗?根据 [ptr.launder] 的说明:

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

要求: p 表示内存中一个字节的地址。一个类型与 T 相似且处于其生命周期内的对象 X 位于地址 A 处。通过结果可达的所有存储器字节均可通过 p 可达 (见下文)。

返回值: 类型为 T * 的指针,指向 X

备注: 只要其参数的值可以在核心常量表达式中使用,就可以调用此函数。如果指向对象 Y 的指针值可以访问一个存储字节,则该存储字节是可达的,其中 Y 是占用该存储字节的对象、与 Y 可以相互转换的对象或者如果 Y 是数组元素,则是直接封闭的数组对象。如果 T 是函数类型或 cv void,则程序不合法。

加粗的句子强调了一个困扰我的问题。如果p是无效的指针值,那么任何存储字节怎么可能是可访问的呢?另一方面,如果采用这种解释,std::launder就无法使用。
否则,[basic.life]的“注”中所述,p在(2)处的值是否可以表示存储区域的指针值:

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


3
将工会成员的生命周期终止并不会自动使指针失效(参考https://timsong-cpp.github.io/cppwp/n4659/basic.life#6)。允许以有限的方式使用指针似乎是一个相当明显的迹象,表明它并非无效(可能不能以任何方式使用)。 - StoryTeller - Unslander Monica
std::launder 只有在使用放置 new 时才相关,对吗? - JVApen
2
转发问题是不被赞同的,只是提醒一下。 - Rakete1111
2
@JVApen 不是,但那是更常见的情况。 - Rakete1111
1个回答

8

在注释中明确地允许这样做。

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

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

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

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

这种“在原始对象所占用的存储位置创建新对象”的情况显然适用于此处,因为:

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

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

然而,即使针对联合体的这种解释发生变化,注释也明确提到std::launder可以绕过此限制。

请注意,旧版标准将子对象排除在此规则之外......但是注释明确表示std::launder也可以绕过这个问题。


我知道这一点,这就是为什么我在问题中引用它的原因。你不觉得这段话与basic.type/3相矛盾吗?当对象超出其生命周期时,指针值的类别可能是什么:指向对象的指针,指向对象末尾之后的指针,空指针值,无效指针。我认为指针值不符合这四个类别中的任何一个。 - Oliv
@Oliv:类别仍然是“指向对象的指针”。您无法使用指针访问旧联合成员对象,因为其存储已被重用。但是,您可以使用指针访问新对象,因为这个规则是这样规定的。而且,您始终可以使用指针来访问联合体,因为联合体存在于相同的地址,并且联合体的生命周期尚未结束。 - Ben Voigt
@Oliv:此外,你的问题表明你不知道在这种情况下std::launder是可选的。 - Ben Voigt
你可以回答这个问题:https://dev59.com/vLTma4cB1Zd3GeqP1Bps。 - Oliv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接