std::launder的目的是什么?

What is the purpose of std::launder?

提问人:Barry 提问时间:9/8/2016 最后编辑:L. F.Barry 更新时间:6/3/2023 访问量:53309

问:

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

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

C 内存 ++17 C ++-FAQ 标准

评论

2赞 txtechhelp 9/8/2016
你是在问论文本身还是关于std::launder? 用于“获取指向在存储中创建的对象的指针,该对象由相同类型的现有对象占用,即使它具有 const 或 reference 成员。std::launder
10赞 Paul Rooney 9/8/2016
关于该主题的有用链接。这个问题也 stackoverflow.com/questions/27003727/......
0赞 Damian 5/8/2018
这现已在 VC2017 的 15.7.0 版本中发布
0赞 curiousguy 12/1/2018
根据 std,指针是微不足道的类型,因此洗钱者不会做任何事情。;)

答:

392赞 Nicol Bolas 9/8/2016 #1

std::launder这个名字很贴切,但前提是你知道它的用途。它执行内存清洗

请看论文中的例子:

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

U u = {{ 1 }};

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

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

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

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

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

所以告诉我......什么会回来?u.x.n

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

[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

评论

55赞 druckermanly 9/8/2016
我的tl也是如此;正确博士:“洗钱基本上是针对非UB类型的双关语”?
22赞 user541686 9/8/2016
你能解释一下为什么这是真的吗?“因为 n 是一个常量变量,编译器可以自由地假设 u.x.n 应始终为 1。”标准中哪里这么说?我之所以这么问,是因为你指出的问题似乎在我看来,它首先是错误的。它应该只在假设规则下为真,这在这里失败了。我错过了什么?
16赞 Barry 9/8/2016
我们能在多大程度上回避这种混叠规则?比如UB是怎么回事?template <class T, class U> T* alias_cast(U* ptr) { return std::launder(reinterpret_cast<T*>(ptr)); }
17赞 T.C. 9/9/2016
@Barry非常;如果在地址所代表的地址上没有 T 类型的对象,那么你就打破了 的前提条件,所以谈论结果是没有意义的。ptrlaunder
21赞 underscore_d 9/12/2016
@NicolBolas 一个好的优化编译器将优化你的正确解决方案,在支持的(即松散的对齐)平台上进行就地重新解释。memcpy
10赞 einpoklum 2/13/2021 #2

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

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

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

为什么要引入“std::launder”而不是让编译器来处理它?

...这让我想到了这种含义。std::launder

评论

12赞 Barry 2/13/2021
我不知道,似乎对我进行了确切的清洗:它删除了指针的出处,使其干净,并且需要(重新)读取。我不知道在这种情况下“弄脏”是什么意思。
2赞 benrg 3/23/2021
我同意,如果它指的是洗钱,那么它的名字是完全倒着的,但我认为你不应该说它弄脏了记忆。肮脏的钱无论是否“洗钱”都是肮脏的,但洗钱使人们错误地认为它是干净的。无论是否 ed 脏内存都是脏的,但清洗会使编译器错误地假设它是干净的。std::launderstd::launder
4赞 Ted Lyngmo 7/25/2021
回复:“那个地方现在被弄脏了,不要做出那个假设”——或者,“那个地方被弄脏了,拜托”std::launder
1赞 supercat 8/28/2021
@benrg:被洗过的钱干净的。如果可以证明有人偷了 7,500 美元,洗钱,然后用这笔钱以 7,500 美元的价格购买了一辆二手车,政府可以扣押这辆车,但除非汽车的卖家是盗窃或洗钱的从犯,否则卖家将有权保留这 7,500 美元。
4赞 Jonathan Wakely 2/20/2023
“这个功能与洗涤相反:它弄脏了指向的记忆,”不,它没有。它完全不会影响内存。允许编译器继续保留其关于指向该内存的现有指针的过时假设。但是它不能将这些假设转移到从 返回的新指针,即使它的数值相同。新指针已被清洗。任何旧指针都没有。只有通过新指针的访问才会受到影响。调用只会生成一个新的“洗涤”指针,并且只会影响通过该指针进行的访问。std::launderlaunder
16赞 F.v.S. 12/20/2021 #3

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

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

用于过度激进的恒定折叠/传播的屏障(已放弃)

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

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

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

去虚拟化的障碍

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

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

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

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

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

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

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

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

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

然而,它们似乎在 Clang (LLVM-D47607) 中意外地相关。

评论

0赞 Marc Mutz - mmutz 2/10/2023
“因此,P0137R1直接使用从无符号 char [N] 数组 undefined 中取消引用 reinterpret_cast'd 指针”——请问,在论文中,这到底在哪里定义?我看不出来......
0赞 F.v.S. 2/13/2023
@MarcMutz-mmutz 这篇论文使一些指针具有不同的指针值,即使它们属于同一类型并代表同一字节的地址。
0赞 F.v.S. 2/13/2023
@MarcMutz-mmutz 对于数组和在数组中创建的对象,指向这两个对象的指针不是“指针可互换”的,因此不能将一个指针值转换为另一个指针值。如果延迟错误的指针值,则会导致 UB。unsigned char[N]reinterpret_cast
0赞 Marc Mutz - mmutz 2/14/2023
"reinterpret_castcan't turn [a ] into [a ]“ - 假设 here 的正确用法是将其应用于 a(如果不是:正确的用法是什么),但是,您写道”std::launder 和基于类型的别名分析/严格别名是无关的。也许是这样的:eel.is/c++draft/basic.life#8.2 不适用,那么 eel.is/c++draft/basic.life#note-4 适用?但后者只是一个音符。unsigned char*T*launder()reinterpret_cast<T*>
0赞 user17732522 2/18/2023
@F.V.S.对于具有动态存储持续时间的合格完整对象,恒定折叠/传播问题仍然存在,该对象仍然可以替换,但不能透明。const