volatile关键字的作用是什么?在c++中它能解决什么问题?
就我而言,我从来没有明知肚明地需要它。
volatile关键字的作用是什么?在c++中它能解决什么问题?
就我而言,我从来没有明知肚明地需要它。
当前回答
在实现无锁数据结构时必须使用volatile。否则,编译器可以自由地优化对变量的访问,这将改变语义。
换句话说,volatile告诉编译器对这个变量的访问必须对应于物理内存的读/写操作。
例如,这是Win32 API中InterlockedIncrement的声明方式:
LONG __cdecl InterlockedIncrement(
__inout LONG volatile *Addend
);
其他回答
在标准C中,使用volatile的一个地方是在信号处理程序中。事实上,在标准C中,在信号处理程序中可以安全地做的就是修改易失性sig_atomic_t变量,或者快速退出。事实上,AFAIK,这是标准C中唯一需要使用volatile来避免未定义行为的地方。
ISO/IEC 9899:2011 §7.14.1.1 The signal function ¶5 If the signal occurs other than as the result of calling the abort or raise function, 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 abort function, the _Exit function, the quick_exit function, or the signal function 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 signal function results in a SIG_ERR return, the value of errno is indeterminate.252) 252) If any signal is generated by an asynchronous signal handler, the behavior is undefined.
这意味着在标准C中,你可以这样写:
static volatile sig_atomic_t sig_num = 0;
static void sig_handler(int signum)
{
signal(signum, sig_handler);
sig_num = signum;
}
除此之外就没什么了。
POSIX对于在信号处理程序中可以做的事情要宽容得多,但仍然存在限制(其中一个限制是标准I/O库- printf()等-不能安全地使用)。
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 a volatile-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.
不幸的是,许多编译器并没有确定程序员需要什么样的保证,而是选择提供标准规定的最低限度的保证。这使得volatile远没有它应有的用处。例如,在gcc或clang上,一个程序员需要实现一个基本的“移交互斥量”(一个已经获得并释放互斥量的任务直到另一个任务释放互斥量后才会再次释放互斥量),他必须做以下四件事中的一件:
将互斥量的获取和释放放在编译器不能内联的函数中,并且不能应用整个程序优化。 将互斥锁保护的所有对象限定为volatile——如果所有访问都发生在获得互斥锁之后和释放互斥锁之前,那么就不应该这样做。 使用优化级别0来强制编译器生成代码,就像所有非限定寄存器的对象都是volatile一样。 使用特定于gcc的指令。
相比之下,当使用更适合系统编程的高质量编译器时,例如icc,人们将有另一种选择:
确保在每个需要获取或释放的地方执行volatile-qualified write。
获取一个基本的“传递互斥量”需要一个volatile读取(看看它是否准备好了),并且不应该需要一个volatile写入(另一方在它被交还之前不会试图重新获取它),但是必须执行一个毫无意义的volatile写入仍然比gcc或clang下可用的任何选项都要好。
在开发嵌入式系统或设备驱动程序时,需要使用Volatile,因为在这些驱动程序中需要读写内存映射的硬件设备。特定设备寄存器的内容随时都可能改变,所以你需要volatile关键字来确保这样的访问不会被编译器优化。
我想引用Herb Sutter在GotW #95中的一句话,这有助于理解volatile变量的含义:
C++ volatile variables (which have no analog in languages like C# and Java) are always beyond the scope of this and any other article about the memory model and synchronization. That’s because C++ volatile variables aren’t about threads or communication at all and don’t interact with those things. Rather, a C++ volatile variable should be viewed as portal into a different universe beyond the language — a memory location that by definition does not obey the language’s memory model because that memory location is accessed by hardware (e.g., written to by a daughter card), have more than one address, or is otherwise “strange” and beyond the language. So C++ volatile variables are universally an exception to every guideline about synchronization because are always inherently “racy” and unsynchronizable using the normal tools (mutexes, atomics, etc.) and more generally exist outside all normal of the language and compiler including that they generally cannot be optimized by the compiler (because the compiler isn’t allowed to know their semantics; a volatile int vi; may not behave anything like a normal int, and you can’t even assume that code like vi = 5; int read_back = vi; is guaranteed to result in read_back == 5, or that code like int i = vi; int j = vi; that reads vi twice will result in i == j which will not be true if vi is a hardware counter for example).
如果你正在从内存中的某个点(比如说,一个完全独立的进程/设备/任何东西)读取数据,则需要使用Volatile。
我曾经在纯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);
}
没有volatile,优化器认为循环是无用的(这家伙从不设置值!他疯了,删掉那代码吧!),我的代码会在没有获得信号量的情况下继续运行,从而在以后造成问题。