为什么C++20中弃用了volatile关键字?

52
根据cppreference,C++20中大多数使用volatile关键字的用法都将被弃用。那么volatile的缺点是什么?在不使用volatile时有哪些替代解决方案?

24
有些volatile的用法并没有被弃用,因为它们很有用(例如在直接从指定内存位置加载或存储的代码中使用,比如在设备驱动程序中)。相当多的“弃用用法”与使用特性的能力有关,而太多的程序员错误地将其作为使变量访问变得原子化的手段。C++库现在(自C++11以来)提供了一种正确的方式来确保变量的原子访问,因此,在意图进行原子访问时不合适地使用volatile是不明智的,应该予以避免。 - Peter
4
好的,以下是您需要翻译的内容:https://embeddedartistry.com/newsletters/march-2019-deprecating-volatile/该文章讨论了在C和C++中使用volatile关键字所面临的挑战,并介绍了由于现代编译器对于代码优化方式的改变导致这些挑战变得更加突出的原因。文章指出,使用volatile关键字并不能保证正确地处理内存中的数据,并提供了一些替代方案。http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1152r0.html#why该文档解释了为什么将volatile从C++20标准中删除。它指出,volatile被广泛误用,而且复杂性使其难以正确使用,同时也没有提供必要的表达力。此外,现代编译器已经能够进行更精确的优化,而无需依赖volatile关键字。最后,该文档提供了一些关于如何迁移现有代码的建议。 - FutureJJ
2
@Peter 哪些典型的 volatile 用法是不正确的? - curiousguy
2
那不是正确的方式。有许多设备驱动程序和库使用volatile,只是因为优化器不会删除存储并重新排序代码...我们有std::atomic用于多线程,但对于可能在ring0中的中断处理程序期间仍然会产生汇编递增指令的非多线程代码,volatile就足够了。 - Петър Петров
1个回答

33

有一位C++委员会语言进化主席做了一个很好的演讲来解释为什么。

简要概述,从中删除volatile的地方在标准中没有明确定义的含义,只会引起混乱。


激励(模糊)示例

  • 易失位字段应由硬件手册和/或编译器指定。
  • +=是单个/原子指令吗?++呢?
  • compare_exchange需要多少次读/写操作?如果失败怎么办?
  • void foo(int volatile n)int volatile foo()是什么意思?
  • *vp;是否应该进行加载?(这在标准中已更改两次。)

多线程

在C和C++中,人们过去通常使用volatile来实现线程安全。在C++11中,新增了一些非UB的方法来创建线程之间的同步和共享状态。我建议阅读Back to Basics: Concurrency作为一个很好的入门指南。


2
我似乎无法确定他们是否正在废弃带有volatile限定符的方法。按照https://www.drdobbs.com/cpp/volatile-the-multithreaded-programmers-b/184403766中的方式编写“volatile正确的代码”是一个相当好的模型。我希望他们的意图不是要打破这个模型。 - David Bien
2
意图是强制使用std::atomic<int>,这将正确支持预减和预增 - 即通过正确使用原子指令。然而,在编写既支持多线程又支持非多线程使用的通用代码时,它会导致const_cast<>解决方法,其中模板实现者必须将volatile int(在非多线程编译中)转换为int以避免过时警告。因此,这很烦人并导致额外的工作。 - David Bien
3
我猜最终的问题是:std::atomic<>是否会检测到它没有在多线程环境中编译,在这种情况下,只使用简单的递减和递增而不是原子操作,这可能会导致性能明显降低。 - David Bien
2
我还会假设std::atomic不检查线程是否启用;然而,这并不是滥用volatile进行线程处理的理由。volatile是/曾经用于描述硬件何时可以与程序交互,并且只有禁用优化器的效果,这不是它的预期含义。如果在单线程环境中优化atomic是一个严重的问题,我会要求编译器实现这个功能。但是,我也会想知道为什么您在单线程代码中使用同步特性。 - unDeadHerbs
3
@DavidBien:你可以使用gcc -Wa,-momit-lock-prefix=yes来编译使用std::atomic的单线程代码。这只是通过传递-momit-lock-prefix=yes选项使汇编器忽略lock前缀;它不会让编译器将事物优化到寄存器中,但是add [mem],1只是一个普通的内存目标增量,而不是原子RMW(也不是内存屏障)。这意味着您不必滥用volatile作为atomic<T>的单线程版本,我认为这就是您在现代C ++中提到volatile与多线程有关的原因。 - Peter Cordes
显示剩余25条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接