简体   繁体   English

std::launder 的目的是什么?

[英]What is the purpose of std::launder?

P0137 introduces the function template std::launder and makes many, many changes to the standard in the sections concerning unions, lifetime, and pointers. P0137引入了函数模板std::launder并对有关联合、生命周期和指针的部分中的标准进行了很多很多更改。

What is the problem this paper is solving?这篇论文要解决什么问题? What are the changes to the language that I have to be aware of?我必须注意语言的哪些变化? And what are we launder ing?我们在launder什么?

std::launder is aptly named, though only if you know what it's for. std::launder的命名恰如其分,但std::launder是您知道它的用途。 It performs memory laundering .它执行内存清洗

Consider the example in the paper:考虑论文中的例子:

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

U u = {{ 1 }};

That statement performs aggregate initialization, initializing the first member of U with {1} .该语句执行聚合初始化,使用{1}初始化U的第一个成员。

Because n is a const variable, the compiler is free to assume that uxn shall always be 1.因为n是一个const变量,编译器可以自由地假设uxn始终为 1。

So what happens if we do this:那么如果我们这样做会发生什么:

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

Because X is trivial, we need not destroy the old object before creating a new one in its place, so this is perfectly legal code.因为X是微不足道的,我们不需要在创建一个新对象之前销毁旧对象,所以这是完全合法的代码。 The new object will have its n member be 2.新对象的n成员将为 2。

So tell me... what will uxn return?所以告诉我... uxn返回什么?

The obvious answer will be 2. But that's wrong, because the compiler is allowed to assume that a truly const variable (not merely a const& , but an object variable declared const ) will never change .显而易见的答案是 2。但这是错误的,因为编译器被允许假设一个真正的const变量(不仅仅是一个const& ,而是一个声明为const的对象变量)永远不会改变 But we just changed it.但我们只是改变了它。

[basic.life]/8 spells out the circumstances when it is OK to access the newly created object through variables/pointers/references to the old one. [basic.life]/8阐明了可以通过变量/指针/对旧对象的引用访问新创建的对象的情况。 And having a const member is one of the disqualifying factors.拥有const成员是取消资格的因素之一。

So... how can we talk about uxn properly?那么……我们怎样才能正确地谈论uxn呢?

We have to launder our memory:我们必须清洗我们的记忆:

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

Money laundering is used to prevent people from tracing where you got your money from.洗钱用于防止人们追踪您的资金来源。 Memory laundering is used to prevent the compiler from tracing where you got your object from, thus forcing it to avoid any optimizations that may no longer apply.内存清洗用于防止编译器跟踪您从何处获取对象,从而迫使它避免任何可能不再适用的优化。

Another of the disqualifying factors is if you change the type of the object.另一个不合格的因素是您是否更改了对象的类型。 std::launder can help here too: std::launder也可以在这里提供帮助:

aligned_storage<sizeof(int), alignof(int)>::type data;
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));

[basic.life]/8 tells us that, if you allocate a new object in the storage of the old one, you cannot access the new object through pointers to the old. [basic.life]/8告诉我们,如果在旧对象的存储中分配新对象,则无法通过指向旧对象的指针访问新对象。 launder allows us to side-step that. launder使我们能够launder这一点。

std::launder is a mis-nomer. std::launder是一个误称。 This function performs the opposite of laundering: It soils the pointed-to memory, to remove any expectation the compiler might have regarding the pointed-to value.此函数执行清洗相反的操作:它污染指向内存,以消除编译器可能对指向值的任何期望。 It precludes any compiler optimizations based on such expectations.它排除了基于此类期望的任何编译器优化。

Thus in @NicolBolas' answer, the compiler might be assuming that some memory holds some constant value;因此,在@NicolBolas 的回答中,编译器可能会假设某些内存保存一些常量值; or is uninitialized.或未初始化。 You're telling the compiler: "That place is (now) soiled, don't make that assumption".你告诉编译器:“那个地方(现在)脏了,不要做出这样的假设”。

If you're wondering why the compiler would always stick to its naive expectations in the first place, and would need to you to conspicuously soil things for it - you might want to read this discussion:如果您想知道为什么编译器一开始总是坚持其幼稚的期望,并且需要您明显地为它弄脏东西 - 您可能想阅读以下讨论:

Why introduce `std::launder` rather than have the compiler take care of it?为什么要引入 `std::launder` 而不是让编译器来处理它?

... which lead me to this view of what std::launder means. ...这使我对std::launder含义std::launder这种看法。

I think there are two purposes of std::launder .我认为std::launder有两个目的。

  1. A barrier for一个障碍constant folding/propagation, including不断折叠/传播,包括devirtualization.去虚拟化。
  2. A barrier for fine-grained object-structure-based alias analysis.基于细粒度对象结构的别名分析的障碍。

Barrier for overaggressive constant folding/propagation (abandoned)过度激进的恒定折叠/传播的障碍(已放弃)

Historically, the C++ standard allowed compilers to assume that the value of a const-qualified or reference non-static data member obtained in some ways to be immutable, even if its containing object is non-const and may be reused by placement new.从历史上看,C++ 标准允许编译器假设以某种方式获得的 const 限定或引用非静态数据成员的值是不可变的,即使其包含的对象是非常量的并且可以通过放置 new 重用。

In C++17/ P0137R1 , std::launder is introduced as a functionality that disables the aforementioned (mis-)optimization ( CWG 1776 ), which is needed for std::optional .在 C++17/ P0137R1 中std::launder被引入作为禁用上述(错误)优化( CWG 1776 )的功能,这是std::optional所需要的。 And as discussed in P0532R0 , portable implementations of std::vector and std::deque may also need std::launder , even if they are C++98 components.正如P0532R0 中所讨论的, std::vectorstd::deque可移植实现可能也需要std::launder ,即使它们是 C++98 组件。

Fortunately, such (mis-)optimization is forbidden by RU007 (included in P1971R0 and C++20).幸运的是, RU007 (包含在P1971R0和 C++20 中)禁止这种(错误)优化。 AFAIK there's no compiler performing this (mis-)optimization. AFAIK 没有编译器执行此(错误)优化。

Barrier for devirtualization去虚拟化的障碍

A virtual table pointer (vptr) can be considered constant during the lifetime of its containing polymorphic object, which is needed for devirtualization.虚拟表指针 (vptr) 在其包含的多态对象的生命周期内可以被视为常量,这是去虚拟化所需的。 Given that vptr is not non-static data member, compilers is still allowed to perform devirtualization based on the assumption that the vptr is not changed (ie, either the object is still in its lifetime, or it is reused by a new object of the same dynamic type) in some cases.鉴于 vptr 不是非静态数据成员,编译器仍然允许基于 vptr 没有改变的假设执行去虚拟化(即,对象仍然在其生命周期中,或者它被新的对象重用)相同的动态类型)在某些情况下。

For some unusual uses that replace a polymorphic object with a new object of different dynamic type (shown here ), std::launder is needed as a barrier for devirtualization.对于一些不寻常的用途具有不同的动态类型(示出的新对象替换一个多态对象这里), std::launder需要作为devirtualization的屏障。

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

Barrier for object-structure-based alias analysis基于对象结构的别名分析的障碍

P0137R1 also changes the C++ object model by introducing pointer-interconvertibility. P0137R1还通过引入指针互转换来改变 C++ 对象模型。 IIUC such change enables some "object-structure-based alias analysis" proposed in N4303 . IIUC 此类更改使N4303 中提出的某些“基于对象结构的别名分析”成为可能

As a result, P0137R1 makes the direct use of dereferencing a reinterpret_cast 'd pointer from an unsigned char [N] array undefined, even if the array is providing storage for another object of correct type.因此,P0137R1 直接使用从未定义的unsigned char [N]数组中取消引用reinterpret_cast的指针,即使该数组为另一个正确类型的对象提供存储。 And then std::launder is needed for access to the nested object.然后需要std::launder来访问嵌套对象。

This kind of alias analysis seems overaggressive and may break many useful code bases.这种别名分析似乎过于激进,可能会破坏许多有用的代码库。 AFAIK it's currently not implemented by any compiler. AFAIK 它目前没有被任何编译器实现。

Relation to type-based alias analysis/strict aliasing与基于类型的别名分析/严格别名的关系

IIUC std::launder and type-based alias analysis/strict aliasing are unrelated. IIUC std::launder和基于类型的别名分析/严格别名无关。 std::launder requires that an living object of correct type to be at the provided address. std::launder要求在提供的地址上有一个正确类型的活对象。

However, it seems that they are accidently made related in Clang ( LLVM-D47607 ).然而,它们似乎在 Clang ( LLVM-D47607 ) 中意外地相关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM