Bar*_*rry 223 c++ memory c++-faq c++17 stdlaunder
P0137引入了函数模板, std::launder并在有关联合,生命周期和指针的部分中对标准进行了许多更改.
这篇论文解决了什么问题?我必须注意哪些语言的变化?我们在做什么launder?
Nic*_*las 232
std::launder虽然只是你知道它的用途,但它的名字恰如其分.它执行内存清洗.
考虑一下文中的例子:
struct X { const int n; };
union U { X x; float f; };
...
U u = {{ 1 }};
Run Code Online (Sandbox Code Playgroud)
该语句执行聚合初始化,初始化Uwith 的第一个成员{1}.
因为它n是一个const变量,编译器可以自由地假设它u.x.n应该始终为1.
那么如果我们这样做会发生什么:
X *p = new (&u.x) X {2};
Run Code Online (Sandbox Code Playgroud)
因为它X是微不足道的,所以我们不需要在创建一个新对象之前销毁旧对象,因此这是完全合法的代码.新对象的n成员为2.
那么告诉我......会有什么u.x.n回报?
显而易见的答案是2.但这是错误的,因为允许编译器假设真正的const变量(不仅是a const&,而是声明 的对象变量const)永远不会改变.但我们只是改变了它.
[basic.life]/8说明了通过变量/指针/对旧对象的引用来访问新创建的对象的情况.拥有const会员是不合格的因素之一.
那么......我们怎么能u.x.n正确谈论呢?
我们必须清洗记忆:
assert(*std::launder(&u.x.n) == 2); //Will be true.
Run Code Online (Sandbox Code Playgroud)
洗钱用于防止人们从您从中获取资金的地方进行追踪.内存清洗用于防止编译器跟踪从中获取对象的位置,从而强制它避免任何可能不再适用的优化.
另一个不合格的因素是你改变了对象的类型.std::launder也可以在这里帮忙:
aligned_storage<sizeof(int), alignof(int)>::type data;
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));
Run Code Online (Sandbox Code Playgroud)
[basic.life]/8告诉我们,如果在旧的存储中分配一个新对象,则无法通过指向旧对象的方式访问新对象.launder允许我们支持这一点.
F.v*_*.S. 18
我认为有两个目的std::launder。
从历史上看,C++ 标准允许编译器假设以某种方式获得的 const 限定或引用非静态数据成员的值是不可变的,即使其包含对象是非 const 并且可以通过放置 new 重用。
在 C++17/ P0137R1中,std::launder引入了作为禁用上述(错误)优化 ( CWG 1776 ) 的功能,这是std::optional. 正如P0532R0std::vector中所讨论的,和 的可移植实现std::deque也可能需要std::launder,即使它们是 C++98 组件。
幸运的是, RU007(包含在P1971R0和 C++20 中)禁止这种(错误)优化。据我所知,没有编译器执行这种(错误)优化。
虚拟表指针 (vptr) 在其包含的多态对象的生命周期内可以被视为常量,这是去虚拟化所需要的。鉴于 vptr 不是非静态数据成员,编译器仍然可以基于 vptr 未更改的假设执行去虚拟化(即,该对象仍处于其生命周期内,或者被该对象的新对象重用)。相同的动态类型)在某些情况下。
对于一些不寻常的用途,用不同动态类型的新对象替换多态对象(如图所示),std::launder需要作为去虚拟化的障碍。
IIUC Clang 使用这些语义实现了std::launder( ) ( LLVM-D40218 )。__builtin_launder
P0137R1还通过引入指针互换性改变了 C++ 对象模型。IIUC 这样的改变使得N4303中提出的一些“基于对象结构的别名分析”成为可能。
因此,P0137R1 直接使用未定义数组reinterpret_cast中取消引用 'd 指针unsigned char [N],即使该数组正在为另一个正确类型的对象提供存储。然后std::launder需要访问嵌套对象。
这种别名分析似乎过于激进,可能会破坏许多有用的代码库。AFAIK 目前还没有任何编译器实现它。
IIUCstd::launder和基于类型的别名分析/严格别名无关。std::launder需要正确类型的活动对象位于所提供的地址。
然而,它们似乎在 Clang 中意外地关联起来(LLVM-D47607)。
std::launder是一个误称。此函数执行与清洗相反的操作:它污染指向内存,以消除编译器可能对指向值的任何期望。它排除了基于此类期望的任何编译器优化。
因此,在@NicolBolas 的回答中,编译器可能会假设某些内存保存一些常量值;或未初始化。你告诉编译器:“那个地方(现在)脏了,不要做出这样的假设”。
如果您想知道为什么编译器一开始总是坚持其幼稚的期望,并且需要您明显地为它弄脏东西 - 您可能想阅读以下讨论:
为什么要引入 `std::launder` 而不是让编译器来处理它?
...这使我对这std::launder意味着什么有了这种看法。
| 归档时间: |
|
| 查看次数: |
19205 次 |
| 最近记录: |