为什么存在挥发性?
volatile
关键字有什么作用? 在C++中它解决了什么问题?
就我而言,我从来没有故意需要它。
What does the volatile
keyword do? In C++ what problem does it solve?
In my case, I have never knowingly needed it.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(19)
其他答案已经提到避免一些优化,以便:
每当您需要一个值看起来来自外部时,易失性都是必不可少的并且是不可预测的,并避免基于已知值进行编译器优化,并且当实际未使用结果但您需要计算它时,或者使用它但您想多次计算它以进行基准测试时,您需要计算在精确点开始和结束。
易失性读取就像输入操作(例如
scanf
或cin
的使用):值似乎来自程序外部,因此任何计算依赖于该值的需要在它之后开始。易失性写入类似于输出操作(例如
printf
或使用cout
):该值似乎是在程序外部传递的,因此如果该值取决于计算,需要先完成。因此,可以使用一对易失性读/写来控制基准并使时间测量变得有意义。
如果没有易失性,您的计算可以由编译器之前启动,因为没有什么可以阻止使用时间测量等函数重新排序。
Other answers already mention avoiding some optimization in order to:
Volatile is essential whenever you need a value to appear to come from the outside and be unpredictable and avoid compiler optimizations based on a value being known, and when a result isn't actually used but you need it to be computed, or it's used but you want to compute it several times for a benchmark, and you need the computations to start and end at precise points.
A volatile read is like an input operation (like
scanf
or a use ofcin
): the value seems to come from the outside of the program, so any computation that has a dependency on the value needs to start after it.A volatile write is like an output operation (like
printf
or a use ofcout
): the value seems to be communicated outside of the program, so if the value depends on a computation, it needs to be finished before.So a pair of volatile read/write can be used to tame benchmarks and make time measurement meaningful.
Without volatile, your computation could be started by the compiler before, as nothing would prevent reordering of computations with functions such as time measurement.
我应该提醒您的一个用途是,在信号处理函数中,如果您想访问/修改全局变量(例如,将其标记为 exit = true),则必须将该变量声明为“易失性”。
One use I should remind you is, in the signal handler function, if you want to access/modify a global variable (for example, mark it as exit = true) you have to declare that variable as 'volatile'.
在 C 的早期,编译器会将所有读取和写入左值的操作解释为内存操作,并按照与代码中出现的读取和写入相同的顺序执行。 如果给予编译器一定的自由度来重新排序和合并操作,在许多情况下效率可以大大提高,但这样做存在一个问题。 尽管操作经常以某种顺序指定,仅仅是因为有必要以某种顺序指定它们,因此程序员选择了许多同样好的替代方案之一,但情况并非总是如此。 有时某些操作按特定顺序发生很重要。
到底哪些测序细节是重要的,取决于目标平台和应用领域。 该标准没有提供特别详细的控制,而是选择了一个简单的模型:如果一系列访问是使用不合格的
易失性
的左值完成的,则编译器可能会根据需要对它们进行重新排序和合并。 如果某个操作是使用 易失性 限定的左值完成的,则质量实现应该提供针对其预期平台和应用程序领域的代码可能需要的任何附加排序保证,而不要求程序员使用非标准语法。不幸的是,许多编译器没有确定程序员需要什么保证,而是选择提供标准规定的最低限度的保证。 这使得
易失性
的用处远没有应有的那么大。 例如,在 gcc 或 clang 上,需要实现基本“移交互斥体”的程序员必须执行以下操作:已获取并释放互斥体的任务在另一个任务完成之前不会再次执行此操作)有四件事:将互斥体的获取和释放放在编译器无法内联且无法应用全程序优化的函数中。
将互斥体保护的所有对象限定为
易失性
——如果所有访问都发生在获取互斥体之后和释放它之前,则不需要这样做。使用优化级别 0 强制编译器生成代码,就好像所有未经过注册的对象都是
易失性
。使用特定于 gcc 的指令。
相比之下,当使用更适合系统编程的更高质量的编译器(例如 icc)时,人们将有另一种选择:
易失性
限定的写入是需要的。获取基本的“交接互斥体”需要进行 易失性读取(以查看它是否已准备好),并且不应该需要进行易失性写入(另一方获胜)在将其交还之前不要尝试重新获取它),但必须执行无意义的 易失性 写入仍然比 gcc 或 clang 下可用的任何选项更好。
In the early days of C, compilers would interpret all actions that read and write lvalues as memory operations, to be performed in the same sequence as the reads and writes appeared in the code. Efficiency could be greatly improved in many cases if compilers were given a certain amount of freedom to re-order and consolidate operations, but there was a problem with this. Even though operations were often specified in a certain order merely because it was necessary to specify them in some order, and thus the programmer picked one of many equally-good alternatives, that wasn't always the case. Sometimes it would be important that certain operations occur in a particular sequence.
Exactly which details of sequencing are important will vary depending upon the target platform and application field. Rather than provide particularly detailed control, the Standard opted for a simple model: if a sequence of accesses are done with lvalues that are not qualified
volatile
, a compiler may reorder and consolidate them as it sees fit. If an action is done with avolatile
-qualified lvalue, a quality implementation should offer whatever additional ordering guarantees might be required by code targeting its intended platform and application field, without requiring that programmers use non-standard syntax.Unfortunately, rather than identify what guarantees programmers would need, many compilers have opted instead to offer the bare minimum guarantees mandated by the Standard. This makes
volatile
much less useful than it should be. On gcc or clang, for example, a programmer needing to implement a basic "hand-off mutex" [one where a task that has acquired and released a mutex won't do so again until the other task has done so] must do one of four things:Put the acquisition and release of the mutex in a function that the compiler cannot inline, and to which it cannot apply Whole Program Optimization.
Qualify all the objects guarded by the mutex as
volatile
--something which shouldn't be necessary if all accesses occur after acquiring the mutex and before releasing it.Use optimization level 0 to force the compiler to generate code as though all objects that aren't qualified
register
arevolatile
.Use gcc-specific directives.
By contrast, when using a higher-quality compiler which is more suitable for systems programming, such as icc, one would have another option:
volatile
-qualified write gets performed everyplace an acquire or release is needed.Acquiring a basic "hand-off mutex" requires a
volatile
read (to see if it's ready), and shouldn't require avolatile
write as well (the other side won't try to re-acquire it until it's handed back) but having to perform a meaninglessvolatile
write is still better than any of the options available under gcc or clang.除了 volatile 关键字用于告诉编译器不要优化对某些变量(可以由线程或中断例程修改)的访问之外,它还可以用于消除一些编译器错误 -- 是的 ---。
例如,我在嵌入式平台上工作,编译器对变量的值做出了一些错误的假设。 如果代码没有优化,程序将正常运行。 通过优化(确实需要优化,因为这是一个关键的例程),代码将无法正常工作。 唯一的解决方案(尽管不是很正确)是将“错误”变量声明为易失性。
Beside the fact that the volatile keyword is used for telling the compiler not to optimize the access to some variable (that can be modified by a thread or an interrupt routine), it can be also used to remove some compiler bugs -- YES it can be ---.
For example I worked on an embedded platform were the compiler was making some wrong assuptions regarding a value of a variable. If the code wasn't optimized the program would run ok. With optimizations (which were really needed because it was a critical routine) the code wouldn't work correctly. The only solution (though not very correct) was to declare the 'faulty' variable as volatile.
即使没有
volatile
关键字,您的程序似乎也能工作? 也许这就是原因:正如前面提到的,
易失性
关键字有助于解决类似的情况,但一旦调用外部或非内联函数,似乎几乎没有效果。 例如:
无论有或没有
易失性
,都会生成几乎相同的结果。只要 g() 可以完全内联,编译器就可以看到正在发生的一切,从而可以进行优化。 但是,当程序调用编译器无法看到正在发生的情况的地方时,编译器再做出任何假设都是不安全的。 因此,编译器将生成始终直接从内存读取的代码。
但请注意,当您的函数 g() 变为内联(由于显式更改或由于编译器/链接器的聪明才智)时,如果您忘记了 volatile 关键字,您的代码可能会崩溃!
因此,即使您的程序看起来没有关键字也能正常工作,我还是建议添加
volatile
关键字。 它使未来变化的意图更加清晰、更加有力。Your program seems to work even without
volatile
keyword? Perhaps this is the reason:As mentioned previously the
volatile
keyword helps for cases likeBut there seems to be almost no effect once an external or non-inline function is being called. E.g.:
Then with or without
volatile
almost the same result is generated.As long as g() can be completely inlined, the compiler can see everything that's going on and can therefore optimize. But when the program makes a call to a place where the compiler can't see what's going on, it isn't safe for the compiler to make any assumptions any more. Hence the compiler will generate code that always reads from memory directly.
But beware of the day, when your function g() becomes inline (either due to explicit changes or due to compiler/linker cleverness) then your code might break if you forgot the
volatile
keyword!Therefore I recommend to add the
volatile
keyword even if your program seems to work without. It makes the intention clearer and more robust in respect to future changes.我想引用 Herb Sutter 的话 GotW #95,可以帮助理解
易失性
变量的含义:I would like to quote Herb Sutter's words from his GotW #95, which can help to understand the meaning of the
volatile
variables:所有答案都非常好。 但最重要的是,我想分享一个例子。
下面是一个小 cpp 程序:
现在,让我们生成上述代码的程序集(我将仅粘贴与此处相关的程序集部分):
生成程序集的命令:
以及程序集:
您可以在程序集中看到没有为
sprintf
生成汇编代码,因为编译器假定x
不会在程序外部发生更改。while
循环的情况也是如此。 由于优化,while
循环被完全删除,因为编译器将其视为无用代码,因此直接将5
分配给x
(参见movl $5, x(%rip)
)。当外部进程/硬件将
x
的值更改为x = 8;
和if(x == 8)
之间时,就会出现问题代码>. 我们希望else
块能够工作,但不幸的是编译器已经删除了该部分。现在,为了解决这个问题,在
assembly.cpp 中,让我们将
int x; 更改为
volatile int x; 并快速看到生成的汇编代码:
在这里您可以看到生成了
sprintf
、printf
和while
循环的汇编代码。 优点是如果x
变量被某些外部程序或硬件更改,sprintf
部分代码将被执行。 类似地,while
循环可用于现在忙等待。All answers are excellent. But on the top of that, I would like to share an example.
Below is a little cpp program:
Now, lets generate the assembly of the above code (and I will paste only that portions of the assembly which relevant here):
The command to generate assembly:
And the assembly:
You can see in the assembly that the assembly code was not generated for
sprintf
because the compiler assumed thatx
will not change outside of the program. And same is the case with thewhile
loop.while
loop was altogether removed due to the optimization because compiler saw it as a useless code and thus directly assigned5
tox
(seemovl $5, x(%rip)
).The problem occurs when what if an external process/ hardware would change the value of
x
somewhere betweenx = 8;
andif(x == 8)
. We would expectelse
block to work but unfortunately the compiler has trimmed out that part.Now, in order to solve this, in the
assembly.cpp
, let us changeint x;
tovolatile int x;
and quickly see the assembly code generated:Here you can see that the assembly codes for
sprintf
,printf
andwhile
loop were generated. The advantage is that if thex
variable is changed by some external program or hardware,sprintf
part of the code will be executed. And similarlywhile
loop can be used for busy waiting now.volatile
关键字旨在防止编译器对可能以编译器无法确定的方式更改的对象应用任何优化。声明为
易失性
的对象在优化中被忽略,因为它们的值可以随时被当前代码范围之外的代码更改。 系统始终从内存位置读取易失性对象的当前值,而不是在请求时将其值保留在临时寄存器中,即使先前的指令要求来自同一对象的值。考虑以下情况
1) 全局变量被作用域外的中断服务程序修改。
2) 多线程应用程序中的全局变量。
如果我们不使用 volatile 限定符,可能会出现以下问题
1)打开优化后,代码可能无法按预期工作。
2) 当启用和使用中断时,代码可能无法按预期工作。
Volatile:程序员最好的朋友
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
The
volatile
keyword is intended to prevent the compiler from applying any optimisations on objects that can change in ways that cannot be determined by the compiler.Objects declared as
volatile
are omitted from optimisation because their values can be changed by code outside the scope of current code at any time. The system always reads the current value of avolatile
object from the memory location rather than keeping its value in temporary register at the point it is requested, even if a previous instruction asked for a value from the same object.Consider the following cases
1) Global variables modified by an interrupt service routine outside the scope.
2) Global variables within a multi-threaded application.
If we do not use volatile qualifier, the following problems may arise
1) Code may not work as expected when optimisation is turned on.
2) Code may not work as expected when interrupts are enabled and used.
Volatile: A programmer’s best friend
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
在针对嵌入式开发时,我有一个循环来检查可以在中断处理程序中更改的变量。 如果没有“易失性”,循环就会变成空操作 - 据编译器所知,变量永远不会改变,因此它会优化检查。
同样的事情也适用于在更传统的环境中可能在不同线程中更改的变量,但我们经常进行同步调用,因此编译器在优化方面并不是那么自由。
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.
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.
当编译器坚持优化我希望在单步执行代码时能够看到的变量时,我在调试版本中使用了它。
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.
除了按预期使用它之外,易失性还用于(模板)元编程。 它可用于防止意外重载,因为 volatile 属性(如 const)参与重载决策。
这是合法的; 两个重载都可能是可调用的并且执行几乎相同的操作。
易失性
重载中的强制转换是合法的,因为我们知道 bar 无论如何都不会传递非易失性T
。 不过,易失性
版本的性能更差,因此,如果非易失性f
可用,则永远不要在重载决策中选择该版本。请注意,代码实际上从不依赖于易失性内存访问。
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.
This is legal; both overloads are potentially callable and do almost the same. The cast in the
volatile
overload is legal as we know bar won't pass a non-volatileT
anyway. Thevolatile
version is strictly worse, though, so never chosen in overload resolution if the non-volatilef
is available.Note that the code never actually depends on
volatile
memory access.我在 20 世纪 90 年代初开发的一个大型应用程序包含使用 setjmp 和 longjmp 的基于 C 的异常处理。 对于需要将值保留在充当“catch”子句的代码块中的变量,必须使用 volatile 关键字,以免这些变量存储在寄存器中并被 longjmp 擦除。
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.
在标准 C 中,使用 易失性 的地方之一是信号处理程序。 事实上,在标准 C 中,您在信号处理程序中可以安全地执行的操作就是修改 易失性 sig_atomic_t 变量,或者快速退出。 事实上,据我所知,这是标准 C 中唯一需要使用 volatile 来避免未定义行为的地方。
这意味着在标准 C 中,您可以编写:
,仅此而已。
POSIX 对于信号处理程序中可以执行的操作要宽松得多,但仍然存在限制(限制之一是标准 I/O 库 -
printf()
等 - 不能安全使用)。In Standard C, one of the places to use
volatile
is with a signal handler. In fact, in Standard C, all you can safely do in a signal handler is modify avolatile sig_atomic_t
variable, or exit quickly. Indeed, AFAIK, it is the only place in Standard C that the use ofvolatile
is required to avoid undefined behaviour.That means that in Standard C, you can write:
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).实现无锁数据结构时必须使用 volatile。 否则,编译器可以自由地优化对变量的访问,这将改变语义。
换句话说,易失性告诉编译器对该变量的访问必须对应于物理内存读/写操作。
例如,InterlockedIncrement 在 Win32 API 中是这样声明的:
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.
To put it another way, volatile tells the compiler that accesses to this variable must correspond to a physical memory read/write operation.
For example, this is how InterlockedIncrement is declared in the Win32 API:
来自 Dan Saks 的一篇“不稳定的承诺”文章:
以下是他关于
的三篇文章的链接易失性
关键字:From a "Volatile as a promise" article by Dan Saks:
Here are links to three of his articles regarding the
volatile
keyword:如果您要从内存中的某个位置读取数据(例如,完全独立的进程/设备/任何可能写入的内容),则需要
易失性
。我曾经在直接 C 语言的多处理器系统中使用双端口 RAM。我们使用硬件管理的 16 位值作为信号量来知道其他人何时完成。 本质上我们是这样做的:
如果没有
易失性
,优化器会认为循环毫无用处(这家伙从来没有设置值!他疯了,摆脱那个代码!)并且我的代码将在没有获取信号量的情况下继续进行,导致以后出现问题。volatile
is needed if you are reading from a spot in memory that, say, a completely separate process/device/whatever may write to.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:
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
关键字来确保此类访问不会被编译器优化掉。volatile
is 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 thevolatile
keyword to ensure that such accesses aren't optimised away by the compiler.一些处理器具有超过 64 位精度的浮点寄存器(例如,没有 SSE 的 32 位 x86,请参阅 Peter 的评论)。 这样,如果您对双精度数字运行多个运算,您实际上会得到比将每个中间结果截断为 64 位更高精度的答案。
这通常很好,但这意味着根据编译器分配寄存器和优化的方式,对完全相同的输入执行完全相同的操作会得到不同的结果。 如果需要一致性,则可以使用 volatile 关键字强制每个操作返回内存。
它对于一些没有代数意义但减少浮点误差的算法也很有用,例如卡汉求和。 从代数上来说,它是一个 nop,因此除非某些中间变量是易失性的,否则它通常会被错误地优化。
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.
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.
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.