当前位置: 首页 > 知识库问答 >
问题:

std::launder的目的是什么?

黎承颜
2023-03-14

P0137 引入了函数模板 std::launder,并在有关联合、生存期和指针的部分中对标准进行了许多更改。

这篇论文解决的问题是什么?我需要注意的语言变化是什么?我们在清洗什么?

共有3个答案

胡云瀚
2023-03-14

我认为std::wash有两个目的。

  1. 不断折叠/传播的障碍,包括 去中心化。
  2. 细粒度基于对象结构的别名分析的障碍。

从历史上看,C 标准允许编译器假定以某种方式获得的 const 限定或引用非静态数据成员的值是不可变的,即使其包含对象是非常量并且可以通过放置 new 重用。

在C 17/P0137R1中,< code>std::launder作为一个禁用上述(错误)优化(CWG 1776)的功能引入,这是< code>std::optional所需要的。正如P0532R0中所讨论的,< code>std::vector和< code>std::deque的可移植实现可能也需要< code>std::launder,即使它们是C 98组件。

幸运的是,RU007(包括在P1971R0和C 20中)禁止这种(错误)优化。AFAIK没有编译器执行此(错误)优化。

虚拟表指针(vptr)在其包含多态对象的生命周期内可以被认为是常量,这是去虚拟化所需要的。假设vptr不是非静态数据成员,在某些情况下,编译器仍然允许基于vptr没有改变的假设(即,或者对象仍然在其生存期内,或者它被相同动态类型的新对象重用)来执行去虚拟化。

对于一些用不同动态类型的新对象替换多态对象的不常见的用法(如此处所示),需要< code>std::launder作为反虚拟化的屏障。

IIUC Clang使用这些语义(LLVM-D40218)实现了std::wash__builtin_launder)。

P0137R1还通过引入指针互变性改变了C对象模型。IIUC这种变化使得N4303中提出的“基于对象结构的别名分析”成为可能。

因此,P0137R1直接使用从未定义的无符号char[N]数组中取消引用reinterpret_cast'd指针,即使该数组正在为另一个正确类型的对象提供存储。然后需要std::launder来访问嵌套对象。

这种别名分析似乎过于激进,可能会破坏许多有用的代码库。目前没有任何编译器实现它。

IIUC std::launder和基于类型的别名分析/严格别名是不相关的。< code>std::launder要求正确类型的活动对象位于提供的地址。

然而,它们似乎意外地与Clang(LLVM-D47607)相关。

戚阳
2023-03-14

std::launder是一个名称错误的函数。此函数执行与洗钱相反的操作:它污染指向内存,以消除编译器可能对指向值的任何期望。它排除了基于此类期望的任何编译器优化。

因此,在@NicolBolas的回答中,编译器可能假设某个内存中保存了某个常量值;或者未初始化。你是在告诉编译器:“那个地方(现在)被弄脏了,不要做那样的假设”。

如果你想知道为什么编译器一开始总是坚持它天真的期望,并且需要你为它显著地破坏一些东西——你可能想要阅读这个讨论:

为什么要引入“std::launder”而不是让编译器来处理它?

…这让我对std::launder的含义有了这样的看法。

徐瑞
2023-03-14

std::launder的名字很贴切,尽管只有当你知道它的用途时。它执行内存清洗。

考虑论文中的例子:

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

U u = {{ 1 }};

该语句执行聚合初始化,使用{1}初始化U的第一个成员。

因为n常量变量,编译器可以自由地假设 应始终为1。

那么如果我们这样做会发生什么:

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

因为< code>X很简单,所以我们不需要在创建一个新对象之前销毁旧对象,所以这是完全合法的代码。新对象的< code>n成员将是2。

告诉我...< code>u.x.n会返回什么?

显而易见的答案是2。但这是错误的,因为编译器被允许假设一个真正的< code>const变量(不仅仅是< code>const

[basic.life]/8列出了可以通过变量/指针/对旧对象的引用访问新创建的对象的情况。而拥有const成员是不合格的因素之一。

因此...怎样才能恰当地谈论< code>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允许我们绕过它。

 类似资料:
  • 我知道是一个原子对象。但是原子化到什么程度呢?据我所知,操作可以是原子的。使一个对象原子化到底是什么意思?例如,如果有两个线程同时执行以下代码: 那么整个操作(例如)是原子操作吗?还是对变量atomic(so)进行了更改?

  • 问题内容: 如果可能,如何举例说明如何实现JNDI的用法? 问题答案: JNDI是Java命名和目录接口。它用于分离应用程序 开发人员 和应用程序 部署 人员 的关注点。在编写依赖于数据库的应用程序时,无需担心用于连接该数据库的用户名或密码。JNDI允许开发人员为数据库命名,并依靠部署者将该名称映射到数据库的实际实例。 例如,如果您要编写在Java EE容器中运行的代码,则可以编写此代码来获取JN

  • 问题内容: 我正在阅读angular2引用,发现了这个。我想知道以下参数是什么意思? 问题答案: 该文件对应于TypeScript编译器(tsc)的配置。 这些链接可以为您提供有关这些属性的详细信息: http://www.typescriptlang.org/docs/handbook/tsconfig-json.html http://json.schemastore.org/tsconfig

  • 问题内容: 在Python 3.3中,向该模块添加了一个类: 提供ChainChain类,用于快速链接许多映射,因此可以将它们视为一个单元。它通常比创建新字典并运行多个update()调用要快得多。 例: 它是由动机这个问题,并予以公布的这一个(没有创建)。 据我了解,它是拥有额外字典并使用s进行维护的替代方法。 问题是: 涵盖了哪些用例? 有现实世界的例子吗? 是否在切换到python3的第三方

  • 问题内容: 仅在包装内提供。它继承自,并且只有一个子类(),该子类仅可从包内部使用。 问题答案: 来源。 我猜想MutableBigInteger在内部用于BigInteger繁重的计算,但由于频繁的重新分配而减慢了计算速度。我不确定为什么它不作为java.math的一部分导出。也许对可变值类别有些厌恶? 为了澄清“可变”: 标准BigInteger在整个生命周期中都有一个值,给定两个BigInt

  • 问题内容: 我遇到了一个问题,我分配了一个名为的变量,然后从本质上对用户进行了身份验证,然后检查了是否。我希望它们不一样,应该包含 AnonymousUser。令我惊讶的是,它们是相同的。 样例代码: 然后,我发现prior_user实际上包含django.utils.functional.SimpleLazyObject的实例,因此我假设它是某种惰性查找类型的东西,即,直到实际使用之前,都不会查