[英]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
有两个目的。
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::vector
和std::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 没有编译器执行此(错误)优化。
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
)。
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 它目前没有被任何编译器实现。
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.