问题标签 [stdlaunder]
For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.
c++ - std::launder 的目的是什么?
P0137引入了函数模板 std::launder
,并在有关联合、生命周期和指针的部分对标准进行了许多更改。
这篇论文要解决什么问题?我必须注意的语言变化是什么?我们在做什么launder
?
c++ - std::launder 替代 pre c++17
它就像std::optional
,但不存储额外的布尔值。用户必须确保只有在初始化后才能访问。
如果您想知道为什么我需要如此晦涩的数据结构,请查看此处:https ://gitlab.com/balki/linkedlist/tree/master
问题
- 可以忽略
std::launder
吗?我猜不是。 - 由于
std::launder
仅在 c++17 中可用,如何在 c++14 中实现上述类?boost::optional
并且std::experimental::optional
应该需要类似的功能,或者他们是否使用了编译器特定的魔法?
注意:很容易错过,类型声明为union
. 这意味着构造函数T
实际上没有被调用。参考:https ://gcc.godbolt.org/z/EVpfSN
memory - 安全(且无成本)重新解释大小数据
我想编写自己的“小向量”类型,而第一个障碍是弄清楚如何实现堆栈存储。
我偶然发现了std::aligned_storage
,这似乎是专门为实现任意堆栈存储而设计的,但我不清楚什么是安全的,什么是不安全的。cppreference.com有一个using的示例std::aligned_storage
,我将在此重复:
除了这两条评论说:
注意:
std::launder
从 C++17 开始需要
“as of”条款本身就相当混乱。这是否意味着
此代码不正确或不可移植,应使用可移植版本
std::launder
(在 C++17 中引入),或C++17 对内存别名/重新解释规则进行了重大更改?
超越这一点,std::launder
从性能的角度来看,我关注的是使用。我的理解是,在大多数情况下,允许编译器对内存别名做出非常强的假设(特别是指向不同类型的指针不引用同一内存)以避免冗余内存负载。
我想在编译器方面保持这种级别的混叠确定性(即,从我的小向量访问的访问与对普通或的访问T
同样可优化),尽管从我读过的内容来看,这听起来像一个完整的别名屏障,即编译器必须假设它对清洗指针的来源一无所知。我担心每次都使用它会干扰通常的加载存储消除。T[]
T *
std::launder
operator[]
也许编译器比这更聪明,或者我可能std::launder
首先误解了它的工作原理。无论如何,我真的不觉得我知道我在用这种级别的 C++ 内存黑客做什么。很高兴知道我必须为这个特定的用例做些什么,但如果有人能启发我了解更一般的规则,那将不胜感激。
更新(进一步探索)
进一步阅读这个问题,我目前的理解是,我在此处粘贴的示例在标准下具有未定义的行为,除非std::launder
使用。也就是说,证明我认为未定义行为的较小实验并没有显示 Clang 或 GCC 像标准似乎允许的那样严格。
让我们从在别名指针的情况下显然不安全的事情开始:
正如人们所预料的那样,Clang 和 GCC(启用了优化和严格别名)生成的代码总是返回5.0
; 如果传递了 ay
和z
该别名,则此函数将不会具有“所需的”行为:
但是,当编译器可以看到别名指针的创建时,事情会变得有点奇怪:
在这种情况下,Clang 和 GCC(带有-O3
和-fstrict-aliasing
)都会生成观察到x
through修改的代码z
:
也就是说,编译器并不能保证“利用”未定义的行为。毕竟,它是未定义的。在那种情况下,假设*z = 7
没有任何效果是没有利润的。那么如果我们“激励”编译器利用严格的别名呢?
假设这对;*z = product
的值没有影响显然对编译器有利。*y
这样做将允许编译器将此函数简化为始终返回的函数5
。尽管如此,生成的代码并没有做出这样的假设:
我对这种行为感到很困惑。我知道我们对编译器在存在未定义行为的情况下会做什么的保证为零,但我也很惊讶 Clang 和 GCC 在这些优化方面都没有更具侵略性。这让我想知道我是否误解了标准,或者 Clang 和 GCC 是否对“严格别名”的定义较弱(并且有记录)。
c++ - 如何解释 std::launder 的可达性要求?
该std::launder
函数要求可以通过结果访问的每个字节都可以通过参数访问。“可达”定义如下:
一个字节的存储空间可通过指向对象Y的指针值访问,如果它位于Y占用的存储空间内,则可以与Y指针互转换的对象,或者如果Y是数组元素,则指向立即封闭的数组对象。
根据对另一个问题的回答,这个限制“......意味着您不能使用launder
获取一个指针,该指针允许您访问比源指针值允许的更多字节,因为未定义行为的痛苦。”
这对于 TC 给出的示例是有意义的,但我不明白在原始对象已被新对象替换的情况下如何解释它,这是预期的原始目的std::launder
。该标准具有以下示例:
在这种情况下,到std::launder
调用时间时,指向的对象——p
原始X
对象——已经不复存在,因为在它占用的存储中创建一个新对象已经隐含地结束了它的生命周期([basic.寿命]/1.4)。因此,似乎没有可到达的字节,p
因为p
它不指向任何对象Y。这显然不是预期的阅读内容,因为它会调用std::launder
示例中的未定义行为。
- 我误解了这里的措辞,还是措辞有缺陷?
- 预期的含义是什么,这将使示例有效?
c++ - 如何在 C++ 中获得没有 UB 的浮点位表示?
据我所知,所有这样做的“传统”方式,即reinterpret_cast
指针和union
withint
和float
字段都是 UB,因为它违反了严格的别名(在 C++ 中,而不是在 C 中)。
那么,如何在没有未定义行为的情况下正确地做到这一点?
我可以对 uint32_t做一个reinterpret_cast
to和 memcpy 吗?char *
或者也许std::launder
会有帮助?
c++ - 带有就地多态容器的 std::launder
我正在为 Game Boy Advance 用 C++ 做一个有些不平凡的项目,并且,作为一个完全没有内存管理的有限平台,我试图避免调用malloc
和动态分配。为此,我已经实现了相当数量的,所谓的“就地多态容器”,它存储从Base
类派生的类型的对象(在类型模板中参数化),然后我有new
对象和使用的函数完美转发调用相应的构造函数。例如,其中一个容器如下所示(也可在此处访问):
std::launder
看了一些关于
特别是如果有Derived
问题的 s(或它Base
本身)有const
成员或引用。我要问的是一个一般指南,不仅针对这个(和另一个)容器,关于使用std::launder
. 你觉得这里怎么样?
因此,建议的解决方案之一是添加一个接收 的内容的指针new (storage) Derived(std::forward<Ts>(ts)...);
,如下所示:
但这基本上意味着代码中每个存在的sizeof(void*)
字节开销(在所讨论的体系结构中为 4) 。PointerInterfaceContainer
这似乎不是很多,但如果我想塞满 1024 个容器,每个容器有 128 个字节,那么这个开销就会加起来。另外,它需要第二次内存访问才能访问指针,并且鉴于在 99% 的情况下,Derived
它将具有Base
作为主要基类(这意味着static_cast<Derved*>(curObject)
并且curObject
是相同的位置),这意味着指针将始终指向to storage
,这意味着所有这些开销都是完全没有必要的。
c++ - C++20 中的 std::launder 用例
[1]
有没有必要将p0593r6 添加到 C++20(第 6.7.2.11 节对象模型[intro.object])中std::launder
,而在 C++17 中需要相同的用例std::launder
,或者它们完全正交?
[2]
[ptr::launder]规范中的示例是:
@Nicol Bolas在此 SO answer中给出了另一个示例,使用指向有效存储但类型不同的指针:
是否有其他用例,与允许投射两个不可透明替换的对象无关,用于使用std::launder
?
具体来说:
- 从 A* 到 B* 的reinterpret_cast是否都是指针互转换的
std::launder
,在任何情况下都可能需要使用?(即两个指针可以指针互转换但不能透明地替换吗?规范在这两个术语之间没有关联)。 - 从void*到 T* 的reinterpret_cast是否需要使用?
std::launder
- 下面的代码是否需要使用
std::launder
?如果是这样,在规范中的哪种情况下需要这样做?
受此讨论启发的具有引用成员的结构:
c++ - 在具有参考字段的类上放置新的
这是来自 C++20 规范 ( [basic.life]/8 ) 的代码示例:
以下行为是否合法或未定义:
万一是非法的,会std::launder
使其合法吗?应该在哪里添加?
注意: p0532r0 (第 5 页)针对类似情况使用洗钱。
如果它是合法的,它如何在没有“指针优化障碍”(即std::launder
)的情况下工作?我们如何避免编译器缓存 的值c1.i
?
该问题与关于std::optional
.
该问题也非常相似地适用于常量字段(即,如果上面i
是struct C
:)const int i
。
编辑
正如@Language Lawyer在下面的答案中指出的那样,似乎在 C++20 中规则已更改,以响应RU007/US042 NB 评论。
C++17 规范 [ptr.launder] (§ 21.6.4.4): --emphasis mine --
[注:如果在同类型的现有对象占用的存储空间中创建新对象,则可以使用指向原始对象的指针来引用新对象,除非该类型包含 const 或引用成员;在后一种情况下,此函数可用于获取指向新对象的可用指针。……结束注]
规范中的 C++17 [ptr.launder] 代码示例(第 21.6.4.5 节):
C++20 [ptr.launder] 规范(§ 17.6.4.5):
[注:如果在已存在的同类型对象占用的存储空间中创建新对象,则可以使用指向原始对象的指针来引用新对象,除非其完整对象是const对象或基类子对象;在后一种情况下,此函数可用于获取指向新对象的可用指针。……结束注]
注意部分:
除非该类型包含 const 或引用成员;
出现在 C++17 中的在 C++20 中被删除,并且示例相应地进行了更改。
规范中的 C++20 [ptr.launder] 代码示例(第 17.6.4.6 节):
因此,显然有问题的代码在 C++20 中是合法的,而对于 C++17,它需要std::launder
在访问新对象时使用。
开放式问题:
在 C++14 或之前(当
std::launder
不存在时)这样的代码是什么情况?可能是 UB - 这就是std::launder
被带到游戏中的原因,对吧?如果在 C++20 中我们不需要
std::launder
这种情况,编译器如何理解在没有我们帮助的情况下正在操纵引用(即没有“指针优化屏障”)以避免缓存引用值?
类似的问题here , here , here和here得到了相互矛盾的答案,有些人认为这是一种有效的语法,但建议重写它。std::launder
在不同的 C++ 版本中,我专注于语法的有效性和对 的需求(是或否) 。
c++ - 为什么要引入 `std::launder` 而不是让编译器处理它?
我刚读过
坦率地说,我只能摸不着头脑。
让我们从@NicolBolas 接受的答案中的第二个示例开始:
[basic.life]/8 告诉我们,如果在旧对象的存储中分配新对象,则无法通过指向旧对象的指针访问新对象。
std::launder
允许我们回避这一点。
那么,为什么不直接更改语言标准,以便data
通过 a进行访问reinterpret_cast<int*>(&data)
是有效/适当的呢?在现实生活中,洗钱是向法律隐瞒现实的一种方式。但我们没有什么可隐瞒的——我们在这里做的是完全合法的事情。std::launder()
那么,当编译器注意到我们正在data
以这种方式访问时,为什么不能将其行为更改为它的行为呢?
上第一个例子:
因为 X 是微不足道的,我们不需要在创建一个新对象之前销毁旧对象,所以这是完全合法的代码。新对象的 n 成员将为 2。
所以告诉我...什么会
u.x.n
返回?显而易见的答案是 2。但这是错误的,因为允许编译器假设一个真正的
const
变量(不仅仅是一个 const&,而是一个声明的对象变量const
)永远不会改变。但我们只是改变了它。
那么为什么不让编译器在我们编写这种代码时不允许做出假设,通过指针访问常量字段呢?
为什么让这个伪函数在形式语言语义上打一个洞是合理的,而不是根据代码是否像这些示例中那样将语义设置为它们需要的语义?
c++ - 使用 std::launder 实现内存缓冲区
如果之前已经回答过这个问题,我深表歉意,我只是无法在最后找到问题的确切答案。
这是一个简化版本的集合。
这是我的问题。(总结评论中出现的问题)。
- 分配内存时,分配它并保留
T*
指针是否足够buffer_start
?还是仍然需要原始内存指针?(我没有看到原因,但直到几天前我才知道reintpret_cast<T*>
会导致 UB)。 - 如果我想访问集合中的元素,我应该使用
std::launder(reinterpret_cast<T*>(buffer_start + i))
,因为我已经放置了新分配或std::launder(buffer_start)
为我解决了这个问题(它甚至有帮助,或者只是没有效果),如果我记得每次删除一个元素时都执行它收藏?(删除元素时甚至需要它吗?)。 delete
在析构函数中使用正确吗?- 使用构造元素是否安全,
new(buffer_start+size)
或者我实际上需要原始内存指针?