ChatGPT解决这个技术问题 Extra ChatGPT

std::launder 的目的是什么?

P0137 引入了函数模板 std::launder,并在有关联合、生命周期和指针的部分中对标准进行了许多更改。

这篇论文要解决什么问题?我必须注意的语言变化是什么?我们launder在做什么?

您问的是论文本身还是std::launderstd::launder 用于“获取指向由相同类型的现有对象占用的存储中创建的对象的指针,即使它具有 const 或引用成员。”
有用的link。还有这个问题stackoverflow.com/questions/27003727/…
现在已经在 VC2017 的 15.7.0 版本中发布
根据标准,指针是微不足道的类型,因此 launder 不会做任何事情。 ;)

C
Community

std::launder 的名称恰如其分,但前提是您知道它的用途。它执行内存清洗

考虑论文中的示例:

struct X { const int n; };
union U { X x; float f; };
...

U u = {{ 1 }};

该语句执行聚合初始化,用 {1} 初始化 U 的第一个成员。

因为 n 是一个 const 变量,所以编译器可以自由假设 u.x.n始终为 1。

那么如果我们这样做会发生什么:

X *p = new (&u.x) X {2};

因为 X 是微不足道的,我们不需要在创建一个新对象之前销毁旧对象,所以这是完全合法的代码。新对象的 n 成员将为 2。

那么告诉我... u.x.n 会返回什么?

显而易见的答案是 2。但这是错误的,因为允许编译器假定一个真正的 const 变量(不仅仅是一个 const&,而是一个对象变量已声明 const)< em>永远不会改变。但我们只是改变了它。

[basic.life]/8 说明了可以通过变量/指针/对旧对象的引用访问新创建的对象的情况。拥有 const 成员是取消资格的因素之一。

那么...我们怎样才能正确地谈论 u.x.n

我们必须清洗我们的记忆:

assert(*std::launder(&u.x.n) == 2); //Will be true.

洗钱是用来防止人们追踪你的钱从哪里来的。内存清洗用于防止编译器跟踪您从何处获取对象,从而强制它避免任何可能不再适用的优化。

另一个不合格的因素是您是否更改了对象的类型。 std::launder 也可以在这里提供帮助:

alignas(int) char data[sizeof(int)];
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));

[basic.life]/8 告诉我们,如果您在旧对象的存储中分配新对象,则无法通过指向旧对象的指针访问新对象。 launder 允许我们回避这一点。


那么我的 tl;dr 是否正确:“洗钱基本上是针对非 UB 类型的双关语”?
你能解释一下为什么这是真的吗? “因为 n 是一个 const 变量,编译器可以自由假设 u.x.n 应始终为 1。” 标准中哪里有这样的规定?我问是因为您指出的问题似乎在我看来首先是错误的。它应该只在 as-if 规则下为真,在这里失败了。我错过了什么?
我们能在多大程度上回避这个别名规则?比如template <class T, class U> T* alias_cast(U* ptr) { return std::launder(reinterpret_cast<T*>(ptr)); } UB 怎么样?
@巴里非常;如果在 ptr 所代表的地址处没有 T 类型的对象,那么您就破坏了 launder 的前提条件,因此没有必要谈论结果。
@NicolBolas 一个好的优化编译器会将您对 memcpy 的正确解决方案优化为在受支持(即松散对齐)平台上的就地重新解释无论如何
e
einpoklum

std::launder 是用词不当。此函数执行相反的清洗:它弄脏指向的内存,以消除编译器可能对指向的值的任何期望。它排除了基于这种期望的任何编译器优化。

因此,在@NicolBolas 的回答中,编译器可能会假设某些内存具有某个常量值;或未初始化。你是在告诉编译器:“那个地方(现在)被弄脏了,不要做那个假设”。

如果您想知道为什么编译器一开始总是坚持其幼稚的期望,并且需要您显着地为它弄脏东西-您可能想阅读以下讨论:

Why introduce `std::launder` rather than have the compiler take care of it?

...这使我对 std::launder 的含义产生了这种看法。


我不知道,似乎对我进行了彻底的清洗:它正在删除指针的出处,以便它是干净的,并且需要(重新)读取。我不知道在这种情况下“弄脏”是什么意思。
@Barry:任何人都可能扔/写东西的内存很脏,不干净。如果我给你一件没有出处信息的衣服——谁知道它在哪里?你肯定会把它放在脏洗衣篮里洗。
我同意如果 std::launder 是指洗钱,则它的命名完全倒退,但我认为您不应该说它会破坏记忆。脏钱无论是否“洗钱”都是肮脏的,但洗钱使人们错误地认为它是干净的。脏内存是脏的,不管std::launder是否经过处理,但清洗会导致编译器停止错误地认为它是干净的。
回复:“那个地方现在脏了,不要做那个假设” - 或者,“那个地方脏了,请std::launder它”
@benrg:洗过的钱是干净的。如果可以证明有人偷了 7,500 美元,洗了钱,然后用这笔钱以 7,500 美元的价格购买了一辆二手车,政府可能会没收这辆车,但除非汽车的卖家是盗窃或洗钱的从犯,卖方将有权保留 7,500 美元的销售费用。
F
F.v.S.

我认为std::launder有两个目的。

持续折叠/传播的障碍,包括去虚拟化。细粒度的基于对象结构的别名分析的障碍。

过度激进的常数折叠/传播的障碍(被遗弃)

从历史上看,C++ 标准允许编译器假定以某种方式获得的 const 限定或引用非静态数据成员的值是不可变的,即使它的包含对象是非 const 并且可以通过放置 new 重用。

在 C++17/P0137R1 中,std::launder 被引入作为禁用上述(错误)优化 (CWG 1776) 的功能,这是 std::optional 所必需的。正如 P0532R0 中所讨论的,std::vectorstd::deque 的可移植实现也可能需要 std::launder,即使它们是 C++98 组件。

幸运的是,RU007(包含在 P1971R0 和 C++20 中)禁止这种(错误)优化。 AFAIK 没有编译器执行此(错误)优化。

去虚拟化的障碍

虚拟表指针 (vptr) 在其包含的多态对象的生命周期内可以被视为常量,这是去虚拟化所必需的。鉴于 vptr 不是非静态数据成员,编译器仍然允许基于 vptr 未更改的假设执行去虚拟化(即,对象仍处于其生命周期中,或者它被新对象重用)相同的动态类型)在某些情况下。

对于一些用不同动态类型的新对象替换多态对象的不寻常用途(如 here 所示),需要 std::launder 作为去虚拟化的屏障。

IIUC Clang 使用这些语义 (LLVM-D40218) 实现了 std::launder (__builtin_launder)。

基于对象结构的别名分析的障碍

P0137R1 还通过引入指针互转换性改变了 C++ 对象模型。 IIUC 的这种变化使 N4303 中提出的一些“基于对象结构的别名分析”成为可能。

因此,P0137R1 直接使用从未定义的 unsigned char [N] 数组中解引用 reinterpret_cast 的指针,即使该数组正在为另一个正确类型的对象提供存储。然后需要 std::launder 才能访问嵌套对象。

这种别名分析似乎过于激进,可能会破坏许多有用的代码库。 AFAIK 目前没有任何编译器实现它。

与基于类型的别名分析/严格别名的关系

IIUC std::launder 和基于类型的别名分析/严格别名无关。 std::launder 要求正确类型的活动对象位于提供的地址。

但是,它们似乎在 Clang (LLVM-D47607) 中意外关联。