std::launder 的目的是什么?
What is the purpose of std::launder?
P0137 引入了函数模板 std::launder
并在有关联合、生命周期和指针的部分对标准进行了很多很多更改。
本文解决的问题是什么?我必须注意的语言有哪些变化?我们 launder
在做什么?
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
return 会怎样?
显而易见的答案是 2。但这是错误的,因为允许编译器假设一个真正的 const
变量(不仅仅是 const&
,而是一个对象变量 声明 const
) 永远不会改变。但我们只是改变了它。
[basic.life]/8 阐明了可以通过 variables/pointers/references 访问新创建的对象到旧对象的情况。拥有 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
允许我们回避这一点。
std::launder
是用词不当。此函数执行 相反 洗钱:它 土壤 指向的内存,以消除编译器可能对指向的值的任何期望.它排除了基于此类期望的任何编译器优化。
因此在@NicolBolas 的回答中,编译器可能会假设某些内存具有某些常量值;或未初始化。你是在告诉编译器:“那个地方(现在)很脏,不要做出那个假设”。
如果您想知道为什么编译器一开始总是坚持其幼稚的期望,并且需要您为它明显地弄脏东西 - 您可能想阅读此讨论:
...这让我想到了 std::launder
的含义。
我觉得std::launder
有两个目的。
常量 folding/propagation 的障碍,包括 去虚拟化。
- 基于细粒度对象结构的别名分析的障碍。
过度攻击常数的障碍folding/propagation(已放弃)
从历史上看,C++ 标准允许编译器假定以某些方式获得的 const 限定或引用非静态数据成员的值是 immutable,即使它包含的对象是非const 并且可以通过 placement new 重用。
在 C++17/P0137R1, std::launder
is introduced as a functionality that disables the aforementioned (mis-)optimization (CWG 1776), which is needed for std::optional
. And as discussed in P0532R0 中,std::vector
和 std::deque
的 table 实现也可能需要 std::launder
,即使它们是 C ++98 个组件。
幸运的是,RU007 (included in P1971R0 和 C++20 禁止这种(错误的)优化。据我所知,没有编译器执行此(错误)优化。
去虚拟化的障碍
虚拟 table 指针 (vptr) 在其包含的多态对象的生命周期内可以被视为常量,这是去虚拟化所必需的。鉴于 vptr 不是非静态数据成员,编译器仍然允许基于 vptr 未更改的假设执行去虚拟化(即对象仍在其生命周期中,或者它被新对象重用相同的动态类型)在某些情况下。
对于一些用不同动态类型的新对象替换多态对象的不寻常用途(显示 here),需要 std::launder
作为去虚拟化的障碍。
IIUC Clang 使用这些语义 (LLVM-D40218) 实现了 std::launder
(__builtin_launder
)。
基于对象结构的别名分析的障碍
P0137R1 also changes the C++ object model by introducing pointer-interconvertibility. IIUC such change enables some "object-structure-based alias analysis" proposed in N4303.
因此,P0137R1 直接使用从未定义的 unsigned char [N]
数组取消引用 reinterpret_cast
的指针,即使该数组正在为另一个正确类型的对象提供存储。然后需要 std::launder
才能访问嵌套对象。
这种别名分析似乎过于激进,可能会破坏许多有用的代码库。据我所知,它目前还没有被任何编译器实现。
与基于类型的别名的关系analysis/strict 别名
IIUC std::launder
和基于类型的别名 analysis/strict 别名无关。 std::launder
要求正确类型的活动对象位于提供的地址。
不过,似乎在 Clang (LLVM-D47607) 中不小心将它们关联起来了。
P0137 引入了函数模板 std::launder
并在有关联合、生命周期和指针的部分对标准进行了很多很多更改。
本文解决的问题是什么?我必须注意的语言有哪些变化?我们 launder
在做什么?
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
return 会怎样?
显而易见的答案是 2。但这是错误的,因为允许编译器假设一个真正的 const
变量(不仅仅是 const&
,而是一个对象变量 声明 const
) 永远不会改变。但我们只是改变了它。
[basic.life]/8 阐明了可以通过 variables/pointers/references 访问新创建的对象到旧对象的情况。拥有 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
允许我们回避这一点。
std::launder
是用词不当。此函数执行 相反 洗钱:它 土壤 指向的内存,以消除编译器可能对指向的值的任何期望.它排除了基于此类期望的任何编译器优化。
因此在@NicolBolas 的回答中,编译器可能会假设某些内存具有某些常量值;或未初始化。你是在告诉编译器:“那个地方(现在)很脏,不要做出那个假设”。
如果您想知道为什么编译器一开始总是坚持其幼稚的期望,并且需要您为它明显地弄脏东西 - 您可能想阅读此讨论:
...这让我想到了 std::launder
的含义。
我觉得std::launder
有两个目的。
常量 folding/propagation 的障碍,包括去虚拟化。- 基于细粒度对象结构的别名分析的障碍。
过度攻击常数的障碍folding/propagation(已放弃)
从历史上看,C++ 标准允许编译器假定以某些方式获得的 const 限定或引用非静态数据成员的值是 immutable,即使它包含的对象是非const 并且可以通过 placement new 重用。
在 C++17/P0137R1, std::launder
is introduced as a functionality that disables the aforementioned (mis-)optimization (CWG 1776), which is needed for std::optional
. And as discussed in P0532R0 中,std::vector
和 std::deque
的 table 实现也可能需要 std::launder
,即使它们是 C ++98 个组件。
幸运的是,RU007 (included in P1971R0 和 C++20 禁止这种(错误的)优化。据我所知,没有编译器执行此(错误)优化。
去虚拟化的障碍
虚拟 table 指针 (vptr) 在其包含的多态对象的生命周期内可以被视为常量,这是去虚拟化所必需的。鉴于 vptr 不是非静态数据成员,编译器仍然允许基于 vptr 未更改的假设执行去虚拟化(即对象仍在其生命周期中,或者它被新对象重用相同的动态类型)在某些情况下。
对于一些用不同动态类型的新对象替换多态对象的不寻常用途(显示 here),需要 std::launder
作为去虚拟化的障碍。
IIUC Clang 使用这些语义 (LLVM-D40218) 实现了 std::launder
(__builtin_launder
)。
基于对象结构的别名分析的障碍
P0137R1 also changes the C++ object model by introducing pointer-interconvertibility. IIUC such change enables some "object-structure-based alias analysis" proposed in N4303.
因此,P0137R1 直接使用从未定义的 unsigned char [N]
数组取消引用 reinterpret_cast
的指针,即使该数组正在为另一个正确类型的对象提供存储。然后需要 std::launder
才能访问嵌套对象。
这种别名分析似乎过于激进,可能会破坏许多有用的代码库。据我所知,它目前还没有被任何编译器实现。
与基于类型的别名的关系analysis/strict 别名
IIUC std::launder
和基于类型的别名 analysis/strict 别名无关。 std::launder
要求正确类型的活动对象位于提供的地址。
不过,似乎在 Clang (LLVM-D47607) 中不小心将它们关联起来了。