【问题标题】:Why do std::atomic methods offer volatile overloads if volatile is unnecessary?如果不需要 volatile,为什么 std::atomic 方法会提供 volatile 重载?
【发布时间】:2019-12-27 09:16:03
【问题描述】:

this good answer 说:

volatilestd::atomic 一起使用时完全不需要。

但是,std::atomic_fecth_sub 提供了一个重载函数:

template<class T>
T atomic_fetch_sub(volatile std::atomic<T>* obj,
    typename std::atomic<T>::difference_type arg ) noexcept;

我的问题是:

如果volatile对于std::atomic来说完全没有必要,为什么C++标准要为其提供重载函数?

【问题讨论】:

  • 那是std::atomic*,不是std::atomic
  • 对我来说,volatile std::atomic&lt;T&gt;* obj这个参数意味着:在实践中,volatile std::atomic&lt;T&gt; var;等定义很常见。
  • Volatile 只做一件事。另一个原子。最终将它们结合起来并非不可想象。
  • @DeanSeo 我首先进入了同一个陷阱,但后来才想起它是怎么回事。请注意,volatile 适用于std::atomic,但不适用于std::atomic*。考虑到这一点,恕我直言,这个问题是有道理的。对于指向某物的 volatile 指针,它必须是 std::atomic&lt;T&gt; *volatile(对于指向 volatile 的 volatile std::atomic&lt;T&gt; *volatilestd::atomic&lt;T&gt; volatile*volatile 的 volatile 指针(因为类型说明符和限定符可能有任何顺序))。我必须承认这些 C++ 类型限定符非常棘手...... ;-)
  • @Scheff 感谢您的更正!就像const 限定词一样,但我的眼睛不习惯volatile .... 确实很有趣。

标签: c++ c++11 standards atomic volatile


【解决方案1】:

如果 volatile 对于 std::atomic 完全不需要,

因为有问题的陈述与提供答案的问题相关。

就 C++ 内存模型中访问的原子性和可见性而言(这是问题所在),atomic 就是您所需要的。但是,volatile 的用例主要是关于与外部系统的通信。诸如内存映射设备之类的东西可以在 C++ 内存模型的范围之外进行更新。

这些功能不重叠。也就是说,volatile 并不意味着 C++ 原子性/可见性,C++ 原子性/可见性并不意味着从内存模型外部更新值。但是,您可能需要 C++ 原子性/可见性以及外部更新。

也许atomic 对象存在于某个共享进程内存中。 C++ 内存模型不知道在程序之外发生的事情,所以单独atomic 不会做任何事情来确保程序外部操作的可见性。但是volatile可以。

volatile 对于atomic 在 C++ 内存模型中完成其工作并不是必要。但对于特定用户,他们可能需要atomicvolatile。因此过载。

【讨论】:

  • volatile 关键字准确地暗示了底层 CPU 在处理内存操作时所暗示的内容;如果 CPU 正常(所有 CPU 都允许),则 volatile 对象上的数据竞争是正常的。对于标量,易失性访问在 CPU 上是原子的,保证它是原子的。 (对于复合类型,没有可靠的映射。)
【解决方案2】:

与任何标量一样,volatile 应用于原子对象可确保汇编代码完全遵循高级 C 或 C++ 代码。

(是的,它在 C 和 C++ 中的工作方式相同;不,没有称为 C/C++ 的语言;是的,许多基本块的设计有意在 C 和 C++ 中是相同的,其中包括原子的语义。)

这意味着从未对原子对象的这些操作进行任何优化,它们的效果是“立即”“可见”;请注意,立即在 MT 程序中基本上没有意义,因为操作可以重新排序,因此没有明确定义的全局“时钟”,可见性取决于 CPU 的保证。但是在汇编/二进制代码中表示的 volatile 限定对象(包括 volatile 原子)上的操作顺序永远不会被编译器更改,您可以签入 asm 输出或反汇编 - 但您不能在运行时签入另一个线程,因为关于对象的更改如何被其他线程可见的 volatile 限定没有暗示任何内容。

一种将 volatile 语义的“无优化”保证形式化的方法是在程序或线程上使用停止信号,然后使用调试器或类似 ptrace 的函数来检查特定对象;所有 volatile 对象都将具有暂停线程的顺序执行所允许的状态,根据所选 ABI 表示(当编译器标志允许选择 ABI 时)。

所以volatile 只对特定的 ABI 有意义; volatile 存在于 C 和 C++ 中这一事实意味着 ABI 中涵盖对象表示的部分是任何标准讨论的一部分。

对外部的调用,即单独编译的函数(根据定义,即在所有优化后编译,从而排除全局优化),具有相同级别的不优化保证;它们是根据 ABI 的调用约定完成的,并且所有可能被调用函数访问的对象都根据 ABI 的对象表示部分表示。 (这就是我所说的 ABI 边界。)编译器不必支持单独编译和调用 ABI 的函数,但如果它们不支持,你甚至不能同时拥有 C 和 C++,因为它们是基于绝对不同的语言不同的标准(在核心语言级别没有共享引用),混合它们的唯一方法是在 ABI 级别。 (没有正式的保证 extern "C" 确实支持与当前使用的任何 C 编译器的链接;唯一的正式保证是这些“C”调用约定函数可以从同一个 C++ 程序中调用。)

任何时候您必须与由同一编译器编译的其他代码进行交互,您需要一个 ABI 约定; volatile 关键字被明确设计为允许与任何编译器之外的世界进行交互:CPU 世界之外的硬件。

[注意:在 C 和 C++ 中,volatile 关键字也可用于执行程序或线程与信号处理程序之间的程序内部通信;或者对于使用 longjmp 的程序来说更罕见,这显然是罕见的,因为它本质上是 C 的一个(损坏、泄漏、不可用)异常系统。]

【讨论】:

    猜你喜欢
    • 2011-06-19
    • 2012-10-19
    • 2016-03-27
    • 1970-01-01
    • 2020-02-05
    • 2012-11-21
    • 2015-12-28
    • 2021-06-21
    • 2013-01-18
    相关资源
    最近更新 更多