C++ 为什么 volatile 存在?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/72552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:39:04  来源:igfitidea点击:

Why does volatile exist?

c++volatilec++-faq

提问by theschmitzer

What does the volatilekeyword do? In C++ what problem does it solve?

什么是volatile关键词呢?在C++中它解决了什么问题?

In my case, I have never knowingly needed it.

就我而言,我从来没有故意需要它。

回答by Doug T.

volatileis needed if you are reading from a spot in memory that, say, a completely separate process/device/whatever may write to.

volatile如果您正在从内存中的某个位置读取数据,例如一个完全独立的进程/设备/任何可能写入的内容,则需要这样做。

I used to work with dual-port ram in a multiprocessor system in straight C. We used a hardware managed 16 bit value as a semaphore to know when the other guy was done. Essentially we did this:

我曾经在直接 C 的多处理器系统中使用双端口 ram。我们使用硬件管理的 16 位值作为信号量来了解其他人何时完成。基本上我们是这样做的:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Without volatile, the optimizer sees the loop as useless (The guy never sets the value! He's nuts, get rid of that code!) and my code would proceed without having acquired the semaphore, causing problems later on.

没有volatile,优化器认为循环没有用(这家伙从不设置值!他疯了,摆脱那些代码!)并且我的代码将在没有获得信号量的情况下继续进行,从而导致以后出现问题。

回答by ChrisN

volatileis needed when developing embedded systems or device drivers, where you need to read or write a memory-mapped hardware device. The contents of a particular device register could change at any time, so you need the volatilekeyword to ensure that such accesses aren't optimised away by the compiler.

volatile在开发嵌入式系统或设备驱动程序时需要,您需要读取或写入内存映射硬件设备。特定设备寄存器的内容可能随时更改,因此您需要volatile关键字来确保此类访问不会被编译器优化掉。

回答by tfinniga

Some processors have floating point registers that have more than 64 bits of precision (eg. 32-bit x86 without SSE, see Peter's comment). That way, if you run several operations on double-precision numbers, you actually get a higher-precision answer than if you were to truncate each intermediate result to 64 bits.

一些处理器具有超过 64 位精度的浮点寄存器(例如,没有 SSE 的 32 位 x86,请参阅 Peter 的评论)。这样,如果对双精度数运行多个运算,实际上得到的结果比将每个中间结果截断为 64 位的结果精度更高。

This is usually great, but it means that depending on how the compiler assigned registers and did optimizations you'll have different results for the exact same operations on the exact same inputs. If you need consistency then you can force each operation to go back to memory by using the volatile keyword.

这通常很好,但这意味着根据编译器如何分配寄存器和进行优化,对于完全相同的输入上的完全相同的操作,您将获得不同的结果。如果您需要一致性,那么您可以使用 volatile 关键字强制每个操作返回内存。

It's also useful for some algorithms that make no algebraic sense but reduce floating point error, such as Kahan summation. Algebraicly it's a nop, so it will often get incorrectly optimized out unless some intermediate variables are volatile.

它对于一些没有代数意义但减少浮点误差的算法也很有用,例如 Kahan 求和。代数上它是一个 nop,所以它通常会被错误地优化掉,除非一些中间变量是 volatile。

回答by MikeZ

From a "Volatile as a promise"article by Dan Saks:

来自Dan Saks的“Volatile as a promise”文章:

(...) a volatile object is one whose value might change spontaneously. That is, when you declare an object to be volatile, you're telling the compiler that the object might change state even though no statements in the program appear to change it."

(...) 一个 volatile 对象是一个其值可能会自发改变的对象。也就是说,当你将一个对象声明为 volatile 时,你是在告诉编译器该对象可能会改变状态,即使程序中的语句似乎没有改变它。”

Here are links to three of his articles regarding the volatilekeyword:

以下是他关于volatile关键字的三篇文章的链接:

回答by Frederik Slijkerman

You MUST use volatile when implementing lock-free data structures. Otherwise the compiler is free to optimize access to the variable, which will change the semantics.

在实现无锁数据结构时,您必须使用 volatile。否则编译器可以自由优化对变量的访问,这将改变语义。

To put it another way, volatile tells the compiler that accesses to this variable must correspond to a physical memory read/write operation.

换句话说, volatile 告诉编译器对这个变量的访问必须对应于物理内存读/写操作。

For example, this is how InterlockedIncrement is declared in the Win32 API:

例如,这是在 Win32 API 中声明 InterlockedIncrement 的方式:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

回答by Jonathan Leffler

In Standard C, one of the places to use volatileis with a signal handler. In fact, in Standard C, all you can safely do in a signal handler is modify a volatile sig_atomic_tvariable, or exit quickly. Indeed, AFAIK, it is the only place in Standard C that the use of volatileis required to avoid undefined behaviour.

在标准 C 中,使用的地方之一volatile是信号处理程序。事实上,在标准 C 中,您可以在信号处理程序中安全地做的就是修改一个volatile sig_atomic_t变量,或者快速退出。事实上,AFAIK,它是标准 C 中唯一volatile需要使用以避免未定义行为的地方。

ISO/IEC 9899:2011 §7.14.1.1 The signalfunction

?5 If the signal occurs other than as the result of calling the abortor raisefunction, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared as volatile sig_atomic_t, or the signal handler calls any function in the standard library other than the abortfunction, the _Exitfunction, the quick_exitfunction, or the signalfunction with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. Furthermore, if such a call to the signalfunction results in a SIG_ERR return, the value of errnois indeterminate.252)

252)If any signal is generated by an asynchronous signal handler, the behavior is undefined.

ISO/IEC 9899:2011 §7.14.1.1signal功能

?5 如果信号不是作为调用abortorraise函数的结果而发生的,则如果信号处理程序引用任何具有静态或线程存储持续时间的对象,并且不是通过分配值的无锁原子对象,则行为未定义到声明为 的对象volatile sig_atomic_t,或信号处理程序调用标准库中除abort函数、_Exit函数、quick_exit函数或signal第一个参数等于引起调用的信号对应的信号号的函数 之外的任何函数处理程序。此外,如果对signal函数的此类调用导致 SIG_ERR 返回,则 的值errno是不确定的。252)

252)如果异步信号处理程序生成任何信号,则行为未定义。

That means that in Standard C, you can write:

这意味着在标准 C 中,您可以编写:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

and not much else.

其他不多。

POSIX is a lot more lenient about what you can do in a signal handler, but there are still limitations (and one of the limitations is that the Standard I/O library — printf()et al — cannot be used safely).

POSIX 对您可以在信号处理程序中执行的操作更加宽容,但仍然存在限制(其中一个限制是标准 I/O 库printf()等无法安全使用)。

回答by Jonathan Leffler

A large application that I used to work on in the early 1990s contained C-based exception handling using setjmp and longjmp. The volatile keyword was necessary on variables whose values needed to be preserved in the block of code that served as the "catch" clause, lest those vars be stored in registers and wiped out by the longjmp.

我在 1990 年代初期曾经开发过的一个大型应用程序包含使用 setjmp 和 longjmp 的基于 C 的异常处理。volatile 关键字对于其值需要保留在充当“catch”子句的代码块中的变量是必需的,以免这些变量存储在寄存器中并被 longjmp 清除。

回答by indentation

I've used it in debug builds when the compiler insists on optimizing away a variable that I want to be able to see as I step through code.

当编译器坚持优化掉一个我希望能够在我逐步执行代码时看到的变量时,我在调试版本中使用了它。

回答by MSalters

Besides using it as intended, volatile is used in (template) metaprogramming. It can be used to prevent accidental overloading, as the volatile attribute (like const) takes part in overload resolution.

除了按预期使用它外, volatile 还用于(模板)元编程中。它可用于防止意外重载,因为 volatile 属性(如 const)参与重载解析。

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

This is legal; both overloads are potentially callable and do almost the same. The cast in the volatileoverload is legal as we know bar won't pass a non-volatile Tanyway. The volatileversion is strictly worse, though, so never chosen in overload resolution if the non-volatile fis available.

这是合法的;两个重载都可能是可调用的,并且几乎相同。在演员volatile超载是合法的,因为我们知道巴不会通过非易失性T反正。volatile但是,该版本更糟糕,因此如果非易失性f可用,则永远不要在重载决议中选择。

Note that the code never actually depends on volatilememory access.

请注意,代码实际上从未依赖于volatile内存访问。

回答by MSalters

Developing for an embedded, I have a loop that checks on a variable that can be changed in an interrupt handler. Without "volatile", the loop becomes a noop - as far as the compiler can tell, the variable never changes, so it optimizes the check away.

为嵌入式开发,我有一个循环来检查可以在中断处理程序中更改的变量。没有“易失性”,循环就变成了一个 noop——据编译器所知,变量永远不会改变,所以它优化了检查。

Same thing would apply to a variable that may be changed in a different thread in a more traditional environment, but there we often do synchronization calls, so compiler is not so free with optimization.

同样的事情也适用于在更传统的环境中可能在不同线程中更改的变量,但我们经常进行同步调用,因此编译器在优化方面并不是那么自由。